Skip to content

Commit b14995b

Browse files
committed
Beginning of GC architecture
1 parent f92e9da commit b14995b

10 files changed

Lines changed: 110 additions & 110 deletions

File tree

src/compiler.cpp

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#include "compiler.hpp"
22
#include "chunk.hpp"
3-
#include "scanner.hpp"
43
#include "gc.hpp"
4+
#include "scanner.hpp"
55

66
#include <cstdint>
77
#include <functional>
@@ -118,10 +118,9 @@ size_t Compiler::declare_upvalue(Upvalue upvalue) {
118118
using lox::scanner::Scanner;
119119
using lox::scanner::TokenType;
120120

121-
Parser::Parser(std::unique_ptr<Scanner> scanner, ObjFunction* fnptr,
122-
StringMap& string_map)
121+
Parser::Parser(std::unique_ptr<Scanner> scanner, ObjFunction* fnptr, GC& gc)
123122
: scanner(std::move(scanner)), current(SENTINEL_EOF),
124-
previous(SENTINEL_EOF), errmsg(std::nullopt), string_map(string_map),
123+
previous(SENTINEL_EOF), errmsg(std::nullopt), gc(gc),
125124
compiler(std::make_unique<Compiler>(fnptr)) {}
126125

127126
void Parser::advance() {
@@ -142,7 +141,7 @@ void Parser::function() {
142141
// updated on the fly later when we parse the function parameters.
143142
int arity = 0;
144143
// TODO: This copies the string. Do we need to?
145-
auto new_fnptr = gc_new<ObjFunction>(fn_name, arity);
144+
auto new_fnptr = gc.alloc<ObjFunction>(fn_name, arity);
146145
auto new_compiler =
147146
std::make_unique<Compiler>(new_fnptr, std::move(compiler));
148147
compiler = std::move(new_compiler);
@@ -407,7 +406,7 @@ void Parser::define_variable(std::string_view var_name) {
407406
}
408407

409408
void Parser::define_global_variable(std::string_view name) {
410-
ObjString* var_name_str = string_map.get_ptr(name);
409+
ObjString* var_name_str = gc.get_string_ptr(name);
411410
// NOTE: we use make_constant here (not emit_constant) because we don't want
412411
// to emit a CONSTANT instruction right now. If we did so then the VM would
413412
// interpret it as a string literal.
@@ -466,7 +465,7 @@ void Parser::named_variable(std::string_view lexeme, bool can_assign) {
466465
// that's a runtime error). We can't error in the compiler because it
467466
// might be defined later after we're done compiling the current
468467
// function.
469-
ObjString* var_name_str = string_map.get_ptr(lexeme);
468+
ObjString* var_name_str = gc.get_string_ptr(lexeme);
470469
size_t constant_index = make_constant(var_name_str);
471470
emit_variable_access(lox::OpCode::SET_GLOBAL, lox::OpCode::GET_GLOBAL,
472471
can_assign, constant_index);
@@ -659,7 +658,7 @@ void Parser::literal(bool _) {
659658
}
660659

661660
void Parser::string(bool _) {
662-
ObjString* obj_str = string_map.get_ptr(previous.lexeme);
661+
ObjString* obj_str = gc.get_string_ptr(previous.lexeme);
663662
emit_constant(obj_str);
664663
}
665664

src/compiler.hpp

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#include "chunk.hpp"
2+
#include "gc.hpp"
23
#include "scanner.hpp"
3-
#include "stringmap.hpp"
44
#include <memory>
55
#include <optional>
66
#include <stdexcept>
@@ -95,8 +95,7 @@ class Compiler {
9595

9696
class Parser {
9797
public:
98-
Parser(std::unique_ptr<scanner::Scanner> scanner, ObjFunction* fnptr,
99-
StringMap& string_map);
98+
Parser(std::unique_ptr<scanner::Scanner> scanner, ObjFunction* fnptr, GC& gc);
10099
void parse();
101100
ObjFunction* finalise_function();
102101

@@ -105,7 +104,7 @@ class Parser {
105104
scanner::Token current;
106105
scanner::Token previous;
107106
std::optional<std::pair<std::string, size_t>> errmsg;
108-
StringMap& string_map;
107+
GC& gc;
109108
std::unique_ptr<Compiler> compiler;
110109

111110
// Interact with scanner

src/gc.cpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
#include "gc.hpp"
2+
#include "value.hpp"
3+
#include <string_view>
4+
#include <unordered_map>
5+
6+
namespace lox {
7+
8+
ObjString* GC::get_string_ptr(std::string_view key) {
9+
auto it = interned_strings.map.find(key);
10+
if (it == interned_strings.map.end()) {
11+
// Not found; create a new one.
12+
ObjString* s = alloc<ObjString>(key);
13+
auto new_pair = interned_strings.map.emplace(std::string(key), s);
14+
it = new_pair.first;
15+
}
16+
return it->second;
17+
}
18+
19+
} // namespace lox

src/gc.hpp

Lines changed: 50 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,53 @@
11
#pragma once
2+
#include "value.hpp"
3+
#include <functional>
4+
#include <string>
5+
#include <string_view>
6+
#include <unordered_map>
27
#include <utility>
38

4-
template <typename T, typename... Args> T* gc_new(Args&&... args) {
5-
T* obj = new T(std::forward<Args>(args)...);
6-
// TODO: add obj to GC tracking system(?)
7-
return obj;
8-
}
9+
namespace lox {
10+
11+
// https://www.cppstories.com/2021/heterogeneous-access-cpp20/
12+
struct string_hash {
13+
using is_transparent = void;
14+
[[nodiscard]] size_t operator()(std::string_view txt) const {
15+
return std::hash<std::string_view>{}(txt);
16+
}
17+
[[nodiscard]] size_t operator()(const std::string& txt) const {
18+
return std::hash<std::string>{}(txt);
19+
}
20+
};
21+
22+
class StringMap {
23+
public:
24+
StringMap() = default;
25+
std::unordered_map<std::string, ObjString*, string_hash, std::equal_to<>> map;
26+
ObjString* get_ptr(std::string_view key);
27+
};
28+
29+
class GC {
30+
public:
31+
// Central allocation function
32+
template <typename T, typename... Args> T* alloc(Args&&... args) {
33+
static_assert(std::is_base_of_v<Obj, T>,
34+
"GC::alloc can only be used to allocate subclasses of Obj");
35+
// Create new object
36+
T* obj = new T(std::forward<Args>(args)...);
37+
// and make it point to the old head
38+
obj->next = head;
39+
// and make it be the new head
40+
head = obj;
41+
return obj;
42+
}
43+
44+
// Get a pointer to an interned ObjString object, creating it if necessary.
45+
ObjString* get_string_ptr(std::string_view);
46+
47+
private:
48+
// First object in the linked list of all objects tracked by the GC.
49+
Obj* head = nullptr;
50+
StringMap interned_strings;
51+
};
52+
53+
} // namespace lox

src/stringmap.cpp

Lines changed: 0 additions & 29 deletions
This file was deleted.

src/stringmap.hpp

Lines changed: 0 additions & 34 deletions
This file was deleted.

src/value.cpp

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#include "value.hpp"
2-
#include "stringmap.hpp"
2+
#include "gc.hpp"
33
#include <iostream>
44

55
namespace {
@@ -23,15 +23,15 @@ std::ostream& operator<<(std::ostream& os, const Value& value) {
2323
return os;
2424
}
2525

26-
Value ObjString::add(const Obj* other, StringMap& string_map) {
26+
Value ObjString::add(const Obj* other, GC& gc) {
2727
if (other->type != ObjType::STRING) {
2828
throw std::runtime_error(
2929
"loxc: add: cannot concatenate non-string to string");
3030
}
3131
// no choice here, we have to concatenate the strings which means allocating
3232
auto other_str = static_cast<const ObjString*>(other);
3333
std::string new_str = value + other_str->value;
34-
return string_map.get_ptr(new_str);
34+
return gc.get_string_ptr(new_str);
3535
}
3636

3737
Value ObjNativeFunction::call(uint8_t arg_count, const Value* args) {
@@ -79,14 +79,14 @@ bool is_equal(const Value& a, const Value& b) {
7979
}
8080
}
8181

82-
Value add(const Value& a, const Value& b, StringMap& string_map) {
82+
Value add(const Value& a, const Value& b, GC& gc) {
8383
if (std::holds_alternative<double>(a) && std::holds_alternative<double>(b)) {
8484
return std::get<double>(a) + std::get<double>(b);
8585
} else if (std::holds_alternative<Obj*>(a) &&
8686
std::holds_alternative<Obj*>(b)) {
8787
auto aptr = std::get<Obj*>(a);
8888
auto bptr = std::get<Obj*>(b);
89-
return aptr->add(bptr, string_map);
89+
return aptr->add(bptr, gc);
9090
} else {
9191
throw std::runtime_error(
9292
"operands to `+` must be two numbers or two strings");

src/value.hpp

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ enum class InterpretResult { OK, COMPILE_ERROR, RUNTIME_ERROR };
1414
enum class ObjType { STRING, FUNCTION, UPVALUE, CLOSURE, NATIVE_FUNCTION };
1515

1616
// Forward declarations
17-
class StringMap; // Actually in stringmap.hpp
17+
class GC;
1818

1919
class Upvalue {
2020
public:
@@ -28,7 +28,11 @@ bool operator==(const Upvalue& a, const Upvalue& b);
2828

2929
class Obj {
3030
public:
31+
// For runtime type information.
3132
ObjType type;
33+
// For our GC's intrusive linked list. This field is managed by the GC, not
34+
// by the Obj itself.
35+
Obj* next = nullptr;
3236

3337
// NOTE: Marking a member function as `virtual` means that C++ will force
3438
// it to use dynamic dispatc (i.e., even if there's an Obj* pointer, it will
@@ -47,7 +51,7 @@ class Obj {
4751
// NOTE: the (= 0) makes this a 'pure virtual' function, meaning that derived
4852
// classes must implement this function.
4953
virtual std::string to_repr() const = 0;
50-
virtual Value add(const Obj* other, StringMap& string_map) = 0;
54+
virtual Value add(const Obj* other, GC& gc) = 0;
5155

5256
// NOTE: `protected` means this constructor can only be called by derived
5357
// classes
@@ -66,7 +70,7 @@ class ObjString : public Obj {
6670
}
6771
#endif
6872
std::string to_repr() const override { return "\"" + value + "\""; }
69-
Value add(const Obj* other, StringMap& string_map) override;
73+
Value add(const Obj* other, GC& gc) override;
7074
};
7175

7276
class ObjFunction : public Obj {
@@ -81,7 +85,7 @@ class ObjFunction : public Obj {
8185

8286
std::string to_repr() const override { return "<fn " + name + ">"; }
8387
// NOTE: If you aren't using the parameters, you can just omit their names!
84-
Value add(const Obj*, StringMap&) override {
88+
Value add(const Obj*, GC&) override {
8589
throw std::runtime_error("loxc: add: cannot add function objects");
8690
}
8791
};
@@ -94,7 +98,7 @@ class ObjUpvalue : public Obj {
9498
: Obj(ObjType::UPVALUE), location(location), closed(std::monostate()) {}
9599

96100
std::string to_repr() const override { return "<upvalue>"; }
97-
Value add(const Obj*, StringMap&) override {
101+
Value add(const Obj*, GC&) override {
98102
throw std::runtime_error("loxc: add: cannot add upvalue objects");
99103
}
100104
};
@@ -109,7 +113,7 @@ class ObjClosure : public Obj {
109113
std::string to_repr() const override {
110114
return "<clos " + function->name + ">";
111115
}
112-
Value add(const Obj*, StringMap&) override {
116+
Value add(const Obj*, GC&) override {
113117
throw std::runtime_error("loxc: add: cannot add function objects");
114118
}
115119
};
@@ -124,7 +128,7 @@ class ObjNativeFunction : public Obj {
124128

125129
Value call(uint8_t arg_count, const Value* args);
126130
std::string to_repr() const override { return "<native fn " + name + ">"; }
127-
Value add(const Obj*, StringMap&) override {
131+
Value add(const Obj*, GC&) override {
128132
throw std::runtime_error("loxc: add: cannot add function objects");
129133
}
130134

@@ -138,7 +142,7 @@ class ObjNativeFunction : public Obj {
138142

139143
bool is_truthy(const Value& value);
140144
bool is_equal(const Value& a, const Value& b);
141-
Value add(const Value& a, const Value& b, StringMap& string_map);
145+
Value add(const Value& a, const Value& b, GC& gc);
142146

143147
// NOTE: Operators like these should (best) be declared in the same namespace
144148
// as the type they operate on. The compiler will be able to find them via

0 commit comments

Comments
 (0)