Skip to content

Commit 4085055

Browse files
mark and sweep
1 parent 847da7d commit 4085055

File tree

10 files changed

+234
-4
lines changed

10 files changed

+234
-4
lines changed

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ add_executable(scheme
2424
src/test.cpp
2525
src/repl.cpp
2626
src/setup.cpp
27+
src/garbage_collection.cpp
2728
include/loguru.cpp
2829
)
2930

src/environment.hpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#pragma once
22
#include <map>
3+
// #include "garbage_collection.hpp"
34
#include "scheme.hpp"
45

56
namespace scm {
@@ -27,6 +28,8 @@ class Environment {
2728
friend void printEnv(Environment& env);
2829
friend Object* getVariable(Environment& env, Object* key);
2930
friend Object* getVariable(Environment& env, std::string& key);
31+
// garbage collection
32+
friend void mark(Environment& env);
3033
};
3134

3235
void define(Environment& env, Object* key, Object* value);

src/garbage_collection.cpp

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
#include "garbage_collection.hpp"
2+
#include <iostream>
3+
#include <list>
4+
#include <loguru.hpp>
5+
#include "environment.hpp"
6+
#include "scheme.hpp"
7+
8+
namespace scm {
9+
10+
// keep track of how many objects we've created in the lifetime of the program
11+
static int totalObjectCount{0};
12+
13+
// keep track of all existing objects
14+
// std::vector<Collectable*> ObjectHeap;
15+
16+
// constructor and destructor for Collectable class
17+
Collectable::Collectable() : marked(false)
18+
{
19+
id = totalObjectCount++;
20+
// keep track of the newly created object
21+
ObjectHeap.push_back(this);
22+
DLOG_IF_F(
23+
INFO, LOG_GARBAGE_COLLECTION, "create Obj:%d (marked: %d)", static_cast<int>(id), marked);
24+
}
25+
26+
Collectable::~Collectable()
27+
{
28+
DLOG_IF_F(ERROR, LOG_GARBAGE_COLLECTION, "delete Obj:%d", static_cast<int>(id));
29+
}
30+
31+
/**
32+
* Marks a scheme object as not to be deleted during garbage collection.
33+
* Will recursively call itself in case of cons objects.
34+
* @param obj the pointer to an object that's to be marked.
35+
*/
36+
void markSchemeObject(Object* obj)
37+
{
38+
switch (getTag(obj)) {
39+
// in most cases, simply mark the object
40+
case TAG_INT:
41+
case TAG_FLOAT:
42+
case TAG_STRING:
43+
case TAG_SYMBOL:
44+
case TAG_NIL:
45+
case TAG_TRUE:
46+
case TAG_FALSE:
47+
case TAG_VOID:
48+
case TAG_EOF:
49+
case TAG_FUNC_BUILTIN:
50+
case TAG_SYNTAX:
51+
obj->marked = true;
52+
break;
53+
// user functions consist of two cons objects, the argument and bodylist
54+
case TAG_FUNC_USER:
55+
markSchemeObject(getUserFunctionArgList(obj));
56+
markSchemeObject(getUserFunctionBodyList(obj));
57+
break;
58+
// recur until we've reached the end of the list
59+
case TAG_CONS:
60+
markSchemeObject(getCar(obj));
61+
markSchemeObject(getCdr(obj));
62+
break;
63+
64+
default:
65+
schemeThrow("tag " + std::to_string(obj->tag) + " isn't handled yet");
66+
break;
67+
}
68+
};
69+
70+
/**
71+
* Mark all objects reachable from a given environment as not to be collected.
72+
* @param env the environment from which an object needs to be reachable in order to be accepted
73+
*/
74+
void mark(Environment& env)
75+
{
76+
for (auto& binding : env.bindings) {
77+
DLOG_IF_F(INFO,
78+
LOG_GARBAGE_COLLECTION,
79+
"marking binding %s | %s",
80+
binding.first.c_str(),
81+
toString(binding.second).c_str());
82+
markSchemeObject(binding.second);
83+
}
84+
}
85+
86+
/**
87+
* Delete all objects that weren't marked or aren't essential.
88+
*/
89+
void sweep()
90+
{
91+
int nObjectsBefore{static_cast<int>(ObjectHeap.size())};
92+
for (auto i = ObjectHeap.begin(); i != ObjectHeap.end();) {
93+
if (!(*i)->marked && !(*i)->essential) {
94+
DLOG_IF_F(INFO,
95+
LOG_GARBAGE_COLLECTION,
96+
"delete %s %s",
97+
tagToString(getTag((Object*)(*i))).c_str(),
98+
toString((Object*)(*i)).c_str());
99+
// TODO: this doesn't seem to work yet -> no deleting as of yet :)
100+
// delete *i;
101+
i = ObjectHeap.erase(i);
102+
}
103+
else {
104+
DLOG_IF_F(INFO,
105+
LOG_GARBAGE_COLLECTION,
106+
"keep %s %s",
107+
tagToString(((Object*)(*i))->tag).c_str(),
108+
toString((Object*)(*i)).c_str());
109+
(*i)->marked = false;
110+
++i;
111+
}
112+
}
113+
int nObjectsAfter{static_cast<int>(ObjectHeap.size())};
114+
DLOG_IF_F(WARNING,
115+
LOG_GARBAGE_COLLECTION,
116+
"cleaned up %d/%d objects",
117+
nObjectsBefore - nObjectsAfter,
118+
nObjectsBefore);
119+
}
120+
121+
/**
122+
* Check which objects are still reachable from a given environment and delete the rest.
123+
* Implementation of a simple mark and sweep algorithm.
124+
* @param env the environment from which the objects need to be reachable in order not to be
125+
* deleted.
126+
*/
127+
void markAndSweep(Environment& env)
128+
{
129+
mark(env);
130+
sweep();
131+
}
132+
133+
} // namespace scm

src/garbage_collection.hpp

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
#pragma once
2+
#include <iostream>
3+
#include <stack>
4+
#include <vector>
5+
// #include "environment.hpp"
6+
7+
namespace scm {
8+
class Environment;
9+
10+
/**
11+
* The base class of every object that's supposed to be visible to the garbage collector.
12+
*/
13+
class Collectable {
14+
public:
15+
// a unique identifier of the object
16+
long id;
17+
// essential objects are never collected
18+
bool essential;
19+
// determines whether the object should be spared during the next sweeping cycle
20+
bool marked;
21+
22+
Collectable();
23+
virtual ~Collectable();
24+
};
25+
26+
static std::vector<Collectable*> ObjectHeap;
27+
void markAndSweep(Environment& env);
28+
void mark(Environment& env);
29+
30+
} // namespace scm

src/memory.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#include "memory.hpp"
22
#include <loguru.hpp>
3+
#include "garbage_collection.hpp"
34
#include "scheme.hpp"
45

56
namespace scm {
@@ -32,6 +33,8 @@ void initializeSingletons()
3233
Object* newSingleton(ObjectTypeTag type)
3334
{
3435
Object* obj{new Object(type)};
36+
// singletons should never be deleted!
37+
obj->essential = true;
3538
return obj;
3639
}
3740

@@ -110,6 +113,8 @@ Object* newBuiltinFunction(std::string name, int numArgs, FunctionTag funcTag, s
110113
{
111114
Object* obj{new Object(TAG_FUNC_BUILTIN)};
112115
obj->value = FuncValue{"primitive:" + name, numArgs, funcTag, helpText};
116+
// builtin functions should never be deleted!
117+
obj->essential = true;
113118
return obj;
114119
};
115120

@@ -127,6 +132,8 @@ Object* newSyntax(std::string name, int numArgs, FunctionTag funcTag, std::strin
127132
{
128133
Object* obj{new Object(TAG_SYNTAX)};
129134
obj->value = FuncValue{"syntax:" + name, numArgs, funcTag, helpText};
135+
// builtin syntax should never be deleted!
136+
obj->essential = true;
130137
return obj;
131138
}
132139

src/operations.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -306,7 +306,8 @@ static Continuation* ifSyntax_Part1()
306306
break;
307307
}
308308
default: {
309-
schemeThrow("evaluation not yet implemented for " + toString(evaluatedCondition));
309+
schemeThrow("evaluation not yet implemented for " + toString(evaluatedCondition) +
310+
" with tag " + tagToString(evaluatedCondition->tag));
310311
break;
311312
}
312313
}

src/parse.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,7 @@ Object* readInput(std::istream* streamPtr, bool isFile)
217217

218218
// remove comments, as they shouldn't be interpreted
219219
line = line.substr(0, line.find(';'));
220+
DLOG_IF_F(INFO, LOG_PARSER, "read line: %s", line.c_str());
220221

221222
// split the read line into lexical elements and store them
222223
std::vector<std::string> split = splitLine(line);

src/repl.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#include <loguru.hpp>
44
#include "environment.hpp"
55
#include "evaluate.hpp"
6+
#include "garbage_collection.hpp"
67
#include "memory.hpp"
78
#include "parse.hpp"
89
#include "scheme.hpp"
@@ -55,6 +56,9 @@ void repl(scm::Environment& env, std::istream* streamPtr, bool isFile)
5556
if (!isFile) {
5657
std::cout << '\n';
5758
}
59+
60+
// collect garbage if necessary
61+
markAndSweep(env);
5862
}
5963
catch (scm::schemeException& e) {
6064
std::cerr << e.what() << '\n';
@@ -120,9 +124,11 @@ void printWelcome()
120124
std::stringstream ss(lambdaGraphics);
121125
std::string token;
122126
std::cout << "\n\n";
127+
std::cout << loguru::terminal_bold() << loguru::terminal_green();
123128
while (std::getline(ss, token, '\n')) {
124129
printCentered(token);
125130
}
131+
std::cout << loguru::terminal_reset();
126132
std::cout << "\n\n";
127133
printCentered("Welcome to Scheme++");
128134
printCentered("Version 1.0.0");

src/scheme.cpp

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ double getFloatValue(Object* obj)
3636
ConsValue getCons(Object* obj)
3737
{
3838
if (!hasTag(obj, TAG_CONS)) {
39-
schemeThrow("tried to get consvalue from non-cons object");
39+
schemeThrow("tried to get consvalue from non-cons object: " + toString(obj));
4040
}
4141
return std::get<ConsValue>(obj->value);
4242
}
@@ -144,9 +144,54 @@ bool LOG_PARSER = 0;
144144
bool LOG_STACK_TRACE = 0;
145145
bool LOG_TESTS = 1;
146146
bool LOG_TRAMPOLINE_TRACE = 0;
147+
bool LOG_GARBAGE_COLLECTION = 0;
147148

148149
// other helper functions
149150

151+
std::string tagToString(ObjectTypeTag tag)
152+
{
153+
switch (tag) {
154+
case TAG_INT:
155+
return "integer";
156+
break;
157+
case TAG_FLOAT:
158+
return "float";
159+
break;
160+
case TAG_STRING:
161+
return "string";
162+
break;
163+
case TAG_SYMBOL:
164+
return "symbol";
165+
break;
166+
case TAG_CONS:
167+
return "cons";
168+
break;
169+
case TAG_NIL:
170+
return "cons";
171+
break;
172+
case TAG_TRUE:
173+
return "true";
174+
break;
175+
case TAG_FALSE:
176+
return "false";
177+
break;
178+
case TAG_FUNC_BUILTIN:
179+
return "builtin function";
180+
break;
181+
case TAG_FUNC_USER:
182+
return "user function";
183+
break;
184+
case TAG_SYNTAX:
185+
return "syntax";
186+
break;
187+
case TAG_VOID:
188+
return "void";
189+
break;
190+
default:
191+
return "unrecognized name";
192+
};
193+
}
194+
150195
static std::string consToString(scm::Object* cons, std::string& str)
151196
{
152197
scm::Object *car, *cdr;

src/scheme.hpp

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include <stack>
77
#include <string>
88
#include <variant>
9+
#include "garbage_collection.hpp"
910

1011
namespace scm {
1112

@@ -18,7 +19,6 @@ enum ObjectTypeTag {
1819
TAG_NIL,
1920
TAG_TRUE,
2021
TAG_FALSE,
21-
TAG_ENV,
2222
TAG_FUNC_BUILTIN,
2323
TAG_FUNC_USER,
2424
TAG_SYNTAX,
@@ -78,10 +78,11 @@ struct UserFuncValue {
7878
Environment* env;
7979
};
8080

81-
struct Object {
81+
struct Object : public Collectable {
8282
ObjectTypeTag tag;
8383
std::variant<int, double, std::string, ConsValue, FuncValue, UserFuncValue> value;
8484
Object(ObjectTypeTag tag) : tag(tag){};
85+
~Object(){};
8586
};
8687

8788
class schemeException : public std::runtime_error {
@@ -131,6 +132,7 @@ bool isString(Object* obj);
131132
bool isNumeric(Object* obj);
132133
bool isFloatingPoint(Object* obj);
133134
bool isOneOf(Object* obj, std::vector<ObjectTypeTag> validTypes);
135+
std::string tagToString(ObjectTypeTag tag);
134136
std::string toString(scm::Object* obj);
135137
static std::string consToString(scm::Object* cons, std::string& str);
136138
std::string prettifyUserFunction(Object* func);
@@ -153,6 +155,7 @@ using ObjectStack = std::stack<Object*>;
153155
using FunctionStack = std::stack<Continuation*>;
154156

155157
// logging activation
158+
extern bool LOG_GARBAGE_COLLECTION;
156159
extern bool LOG_TRAMPOLINE_TRACE;
157160
extern bool LOG_STACK_TRACE;
158161
extern bool LOG_PARSER;

0 commit comments

Comments
 (0)