Skip to content

Commit

Permalink
VM: implement a GC
Browse files Browse the repository at this point in the history
  • Loading branch information
mrunix00 committed Aug 1, 2024
1 parent 8235ec7 commit 4cfc210
Show file tree
Hide file tree
Showing 5 changed files with 172 additions and 40 deletions.
30 changes: 29 additions & 1 deletion include/vm.h
Original file line number Diff line number Diff line change
Expand Up @@ -80,13 +80,18 @@ struct Object {
Array,
} objType;
Object() : objType(Type::Invalid){};
virtual ~Object() = default;
explicit Object(Type type) : objType(type){};
bool marked = false;
};

struct StringObject : public Object {
size_t length;
char *chars;
StringObject(size_t length, char *chars) : Object(Type::String), length(length), chars(chars){};
~StringObject() override {
free(chars);
}
inline bool operator==(StringObject &other) const {
if (length != other.length) return false;
return std::memcmp(chars, other.chars, length) == 0;
Expand All @@ -100,6 +105,9 @@ struct ArrayObject : public Object {
uint64_t *data;
size_t size;
ArrayObject(size_t size, uint64_t *data) : Object(Object::Type::Array), size(size), data(data) {}
~ArrayObject() override {
free(data);
}
inline bool operator==(ArrayObject &other) const {
if (size != other.size) return false;
for (size_t i = 0; i < size; i++) {
Expand Down Expand Up @@ -131,8 +139,10 @@ struct Segment {
std::vector<Instruction> instructions;
std::unordered_map<std::string, Variable> locals;
std::unordered_map<std::string, Variable> functions;
size_t locals_capacity;
size_t number_of_locals;
size_t number_of_local_ptr;
size_t number_of_args{};
size_t number_of_arg_ptr{};
size_t id{};
size_t find_local(const std::string &identifier);
VariableType *returnType{};
Expand All @@ -149,15 +159,22 @@ struct Program {

struct StackFrame {
uint64_t *locals{};
Object **localPointers{};
size_t localsSize{};
size_t localPointersSize{};
size_t segmentIndex{};
size_t currentInstruction{};
};

class VM {
const size_t gcLimit = 1024;

uint64_t *stack;
Object **pointersStack;
size_t stackCapacity;
size_t pointersStackCapacity;
std::vector<StackFrame> callStack;
std::vector<Object *> objects;

public:
VM();
Expand All @@ -171,7 +188,18 @@ class VM {
void pushStack(uint64_t value);
uint64_t popStack();
[[nodiscard]] uint64_t topStack() const;
[[nodiscard]] Object *getPointer(size_t index) const;
void setPointer(size_t index, Object *object);
[[nodiscard]] Object *getGlobalPointer(size_t index) const;
void setGlobalPointer(size_t index, Object *object);
void pushPointer(Object *);
Object *popPointer();
[[nodiscard]] Object *topPointer() const;
void addObject(Object *);
void markAll();
void sweep();

void run(const Program &program);
size_t stackSize{};
size_t pointersStackSize{};
};
6 changes: 3 additions & 3 deletions main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
#include <iostream>

void printTopStack(const VM &vm, const Program &program) {
if (vm.stackSize == 0) return;
if (vm.stackSize == 0 && vm.pointersStackSize == 0) return;
auto number_of_instructions = program.segments[0].instructions.size();
auto last_instruction = program.segments[0].instructions[number_of_instructions - 2];
auto type = getInstructionType(program, last_instruction);
Expand All @@ -17,7 +17,7 @@ void printTopStack(const VM &vm, const Program &program) {
std::cout << (int64_t) vm.topStack() << std::endl;
} break;
case VariableType::Object: {
auto obj = std::bit_cast<Object *>(vm.topStack());
auto obj = vm.topPointer();
if (obj->objType == Object::Type::String) {
std::cout << static_cast<StringObject *>(obj)->chars << std::endl;
} else if (obj->objType == Object::Type::Array) {
Expand All @@ -29,7 +29,7 @@ void printTopStack(const VM &vm, const Program &program) {
}
std::cout << "]" << std::endl;
}
}
} break;
default:
return;
}
Expand Down
17 changes: 9 additions & 8 deletions src/ast.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -219,15 +219,16 @@ void Declaration::compile(Program &program, Segment &segment) const {
segment.declare_function(identifier.token.value,
new FunctionType(returnType, arguments),
program.segments.size());
for (size_t index = 0; index < functionDeclaration->arguments.size(); index++) {
auto argument = functionDeclaration->arguments[index];
newSegment.locals[argument->identifier.token.value] = Variable(
argument->identifier.token.value,
deduceType(program, segment, argument),
index);
for (auto argument: functionDeclaration->arguments) {
auto argType = deduceType(program, segment, argument);
if(argType->type == VariableType::Object ||
argType->type == VariableType::Array) {
newSegment.number_of_arg_ptr++;
} else {
newSegment.number_of_args++;
}
newSegment.declare_variable(argument->identifier.token.value, argType);
}
newSegment.locals_capacity = newSegment.locals.size();
newSegment.number_of_args = arguments.size();
value.value()->compile(program, newSegment);
program.segments.push_back(newSegment);
} break;
Expand Down
151 changes: 127 additions & 24 deletions src/vm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,18 @@ Variable Program::find_function(const Segment &segment, const std::string &ident
}

void Segment::declare_variable(const std::string &name, VariableType *varType) {
locals[name] = Variable(name, varType, locals_capacity);
if (varType->type != VariableType::Type::Function) {
locals_capacity++;
switch (varType->type) {
case VariableType::Object:
case VariableType::Array:
locals[name] = Variable(name, varType, number_of_local_ptr);
number_of_local_ptr++;
break;
default:
locals[name] = Variable(name, varType, number_of_locals);
if (varType->type != VariableType::Type::Function) {
number_of_locals++;
}
break;
}
}
size_t Segment::find_local(const std::string &identifier) {
Expand All @@ -46,33 +55,48 @@ void Segment::declare_function(const std::string &name, VariableType *funcType,

VM::VM() {
stackCapacity = 1024;
pointersStackCapacity = 1024;
stack = (uint64_t *) malloc(stackCapacity * sizeof(uint64_t));
pointersStack = (Object **) malloc(pointersStackCapacity * sizeof(Object *));
if (stack == nullptr)
throw std::runtime_error("Memory allocation failure!");
callStack.push_back(StackFrame{});
}
VM::~VM() {
free(stack);
for (auto stackFrame: callStack)
free(stackFrame.locals);
}
inline void VM::newStackFrame(const Segment &segment) {
auto locals = (uint64_t *) malloc(segment.locals_capacity * sizeof(uint64_t));
if (locals == nullptr) {
throw std::runtime_error("Memory allocation failure!");
uint64_t *locals{};
Object **pointers{};
if (segment.number_of_locals != 0) {
locals = (uint64_t *) malloc(segment.number_of_locals * sizeof(uint64_t));
if (locals == nullptr) {
throw std::runtime_error("Memory allocation failure!");
}
}
if (segment.number_of_local_ptr != 0) {
pointers = (Object **) malloc(segment.number_of_local_ptr * sizeof(Object *));
memset(pointers, 0, segment.number_of_local_ptr * sizeof(Object *));
if (pointers == nullptr) {
throw std::runtime_error("Memory allocation failure!");
}
}
callStack.push_back({
.locals = locals,
.localsSize = segment.locals_capacity,
.localPointers = pointers,
.localsSize = segment.number_of_locals,
.localPointersSize = segment.number_of_local_ptr,
.segmentIndex = segment.id,
.currentInstruction = 0,
});
for (size_t i = segment.number_of_args - 1; i != -1; i--) {
for (size_t i = segment.number_of_args - 1; i != -1; i--)
setLocal(i, popStack());
}
for (size_t i = segment.number_of_arg_ptr - 1; i != -1; i--)
setPointer(i, popPointer());
}
inline void VM::popStackFrame() {
free(callStack.back().locals);
free(callStack.back().localPointers);
callStack.pop_back();
}
inline uint64_t VM::getLocal(const size_t index) const {
Expand Down Expand Up @@ -104,17 +128,83 @@ inline uint64_t VM::popStack() {
uint64_t VM::topStack() const {
return stack[stackSize - 1];
}
Object *VM::getPointer(size_t index) const {
return callStack.back().localPointers[index];
}
void VM::setPointer(size_t index, Object *object) {
callStack.back().localPointers[index] = object;
}
Object *VM::getGlobalPointer(size_t index) const {
return callStack.front().localPointers[index];
}
void VM::setGlobalPointer(size_t index, Object *object) {
callStack.front().localPointers[index] = object;
}
void VM::pushPointer(Object *obj) {
if (pointersStackSize + 1 > pointersStackCapacity) {
pointersStackCapacity *= 2;
auto newPointersStack = (Object **) realloc(pointersStack, pointersStackCapacity * sizeof(Object *));
if (newPointersStack == nullptr) {
throw std::runtime_error("Memory allocation failure!");
}
pointersStack = newPointersStack;
}
pointersStack[pointersStackSize++] = obj;
}
Object *VM::popPointer() {
return pointersStack[--pointersStackSize];
}
Object *VM::topPointer() const {
return pointersStack[pointersStackSize - 1];
}
void VM::addObject(Object *obj) {
objects.push_back(obj);
if (objects.size() > gcLimit) {
markAll();
sweep();
}
}
void VM::markAll() {
for (size_t i = 0; i < pointersStackSize; i++)
pointersStack[i]->marked = true;
for (auto stackFrame: callStack) {
for (size_t i = 0; i < stackFrame.localPointersSize; i++) {
if (stackFrame.localPointers[i] != nullptr)
stackFrame.localPointers[i]->marked = true;
}
}
}
void VM::sweep() {
for (auto it = objects.begin(); it != objects.end();) {
if (!(*it)->marked) {
delete *it;
it = objects.erase(it);
} else {
(*it)->marked = false;
it++;
}
}
}

void VM::run(const Program &program) {
if (callStack.front().localsSize != program.segments.front().locals_capacity) {
callStack.front().localsSize = program.segments.front().locals_capacity;
if (callStack.front().localsSize != program.segments.front().number_of_locals) {
callStack.front().localsSize = program.segments.front().number_of_locals;
auto *newPtr = (uint64_t *) realloc(callStack.front().locals, callStack.front().localsSize * sizeof(uint64_t));
if (newPtr == nullptr) {
throw std::runtime_error("Memory allocation failure!");
} else {
callStack.front().locals = newPtr;
}
}
if (callStack.front().localPointersSize != program.segments.front().number_of_local_ptr) {
callStack.front().localPointersSize = program.segments.front().number_of_local_ptr;
auto *newPtr = (Object **) realloc(callStack.front().localPointers, callStack.front().localPointersSize * sizeof(Object *));
if (newPtr == nullptr) {
throw std::runtime_error("Memory allocation failure!");
} else {
callStack.front().localPointers = newPtr;
}
}
auto *segment = &program.segments[callStack.back().segmentIndex];
for (;;) {
auto &instruction = segment->instructions[callStack.back().currentInstruction];
Expand Down Expand Up @@ -205,31 +295,43 @@ void VM::run(const Program &program) {
auto val = popStack();
pushStack(val - 1);
} break;
case Instruction::StoreGlobalObject:
case Instruction::StoreGlobalObject: {
auto val = popPointer();
setGlobalPointer(instruction.params.index, val);
} break;
case Instruction::StoreGlobalI64: {
auto val = popStack();
setGlobal(instruction.params.index, val);
} break;
case Instruction::StoreLocalObject:
case Instruction::StoreLocalObject: {
auto val = popPointer();
setPointer(instruction.params.index, val);
} break;
case Instruction::StoreLocalI64: {
auto val = popStack();
setLocal(instruction.params.index, val);
} break;
case Instruction::LoadI64: {
pushStack(instruction.params.i64);
} break;
case Instruction::LoadGlobalObject:
case Instruction::LoadGlobalObject: {
auto val = getGlobalPointer(instruction.params.index);
pushPointer(val);
} break;
case Instruction::LoadGlobalI64: {
auto val = getGlobal(instruction.params.index);
pushStack(val);
} break;
case Instruction::LoadLocalObject:
case Instruction::LoadLocalObject: {
auto val = getPointer(instruction.params.index);
pushPointer(val);
} break;
case Instruction::LoadLocalI64: {
auto val = getLocal(instruction.params.index);
pushStack(val);
} break;
case Instruction::LoadObject: {
pushStack(std::bit_cast<uint64_t>(instruction.params.ptr));
pushPointer((Object *) instruction.params.ptr);
} break;
case Instruction::MakeArray: {
auto data = (uint64_t *) malloc(instruction.params.index * sizeof(uint64_t));
Expand All @@ -241,34 +343,35 @@ void VM::run(const Program &program) {
data[i] = val;
}
auto newObject = new ArrayObject(instruction.params.index, data);
pushStack(std::bit_cast<uint64_t>(newObject));
pushPointer(newObject);
addObject(newObject);
} break;
case Instruction::LoadFromLocalArray: {
auto index = popStack();
auto array = std::bit_cast<ArrayObject *>(getLocal(instruction.params.index));
auto array = (ArrayObject *) getPointer(instruction.params.index);
if (index >= array->size) {
throw std::runtime_error("[VM::run] Array index out of bounds!");
}
pushStack(array->data[index]);
} break;
case Instruction::LoadFromGlobalArray: {
auto index = popStack();
auto array = std::bit_cast<ArrayObject *>(getGlobal(instruction.params.index));
auto array = (ArrayObject *) getGlobalPointer(instruction.params.index);
if (index >= array->size) {
throw std::runtime_error("[VM::run] Array index out of bounds!");
}
pushStack(array->data[index]);
} break;
case Instruction::AppendToArray: {
auto array = std::bit_cast<ArrayObject *>(popStack());
auto array = (ArrayObject *) popPointer();
auto val = popStack();
auto data = (uint64_t*) realloc(array->data, (array->size + 1) * sizeof(uint64_t));
auto data = (uint64_t *) realloc(array->data, (array->size + 1) * sizeof(uint64_t));
if (data == nullptr) {
throw std::runtime_error("Memory allocation failure!");
}
array->data = data;
array->data[array->size++] = val;
pushStack(std::bit_cast<uint64_t>(array));
pushPointer(array);
} break;
case Instruction::Exit:
return;
Expand Down
Loading

0 comments on commit 4cfc210

Please sign in to comment.