Skip to content

Commit

Permalink
Add initial outline closure implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
joshuanunn committed Jan 1, 2024
1 parent 7a09996 commit ec50e2c
Show file tree
Hide file tree
Showing 12 changed files with 289 additions and 41 deletions.
69 changes: 66 additions & 3 deletions compiler/code.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ std::map<OpType, std::shared_ptr<Definition>> definitions = {
{OpType::OpSetLocal, new_definition("OpSetLocal", 1)},
{OpType::OpGetLocal, new_definition("OpGetLocal", 1)},
{OpType::OpGetBuiltin, new_definition("OpGetBuiltin", 1)},
{OpType::OpClosure, new_definition("OpClosure", 2, 1)},
};

Definition::Definition(std::string name) : name{name}, operand_widths{} {}
Expand Down Expand Up @@ -76,6 +77,9 @@ std::string fmt_instruction(std::shared_ptr<Definition> def, std::vector<int> op
return def->name;
case 1:
return def->name + " " + std::to_string(operands.at(0));
case 2:
return def->name + " " + std::to_string(operands.at(0)) +
+ " " + std::to_string(operands.at(1));
}

return "ERROR: unhandled operand_count for " + def->name + "\n";
Expand Down Expand Up @@ -109,7 +113,8 @@ std::ostream& operator<<(std::ostream& out, const Instructions& ins) {
Instructions make(OpType op) {
auto [def, ok] = lookup(op);
if (!ok) {
return std::vector<uint8_t>{};
std::cerr << "fatal error: " << def->name << " instruction not defined." << std::endl;
throw std::exception();
}

int instruction_len = std::accumulate(def->operand_widths.begin(), def->operand_widths.end(), 1);
Expand All @@ -128,7 +133,8 @@ Instructions make(OpType op) {
Instructions make(OpType op, int operand) {
auto [def, ok] = lookup(op);
if (!ok) {
return std::vector<uint8_t>{};
std::cerr << "fatal error: " << def->name << " instruction not defined." << std::endl;
throw std::exception();
}

int instruction_len = std::accumulate(def->operand_widths.begin(), def->operand_widths.end(), 1);
Expand All @@ -149,7 +155,64 @@ Instructions make(OpType op, int operand) {
// Store 8-bit instruction directly in a single byte
case 1:
instruction[offset] = static_cast<uint8_t>(operand);
break;
break;
}

return instruction;
}

Instructions make(OpType op, int first_operand, int second_operand) {
auto [def, ok] = lookup(op);
if (!ok) {
std::cerr << "fatal error: " << def->name << " instruction not defined." << std::endl;
throw std::exception();
}

if (def->operand_widths.size() != 2) {
std::cerr << "fatal error: unexpectedly received 2 operands for an " << def->name << " instruction." << std::endl;
throw std::exception();
}

int instruction_len = std::accumulate(def->operand_widths.begin(), def->operand_widths.end(), 1);

std::vector<uint8_t> instruction(instruction_len);
instruction[0] = as_opcode(op);

int offset = 1;
int width;

// Process first operand
width = def->operand_widths.at(0);

switch (width) {
// Store 16-bit instruction as 2 bytes (big endian byte order)
case 2:
instruction[offset] = static_cast<uint16_t>(first_operand) >> 8;
instruction[offset + 1] = static_cast<uint16_t>(first_operand) & 0xFF;
offset += 2;
break;
// Store 8-bit instruction directly in a single byte
case 1:
instruction[offset] = static_cast<uint8_t>(first_operand);
offset += 1;
break;
}

// Process second operand
width = def->operand_widths.at(1);

switch (width) {
// Store 16-bit instruction as 2 bytes (big endian byte order)
case 2:
instruction[offset] = static_cast<uint16_t>(second_operand) >> 8;
instruction[offset + 1] = static_cast<uint16_t>(second_operand) & 0xFF;
offset += 2;
break;
// Store 8-bit instruction directly in a single byte
case 1:
instruction[offset] = static_cast<uint8_t>(second_operand);
offset += 1;
break;
}

return instruction;
Expand Down
5 changes: 4 additions & 1 deletion compiler/code.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ enum class OpType : Opcode {
OpReturn,
OpSetLocal,
OpGetLocal,
OpGetBuiltin
OpGetBuiltin,
OpClosure
};

struct Definition {
Expand Down Expand Up @@ -89,6 +90,8 @@ Instructions make(OpType op);

Instructions make(OpType op, int operand);

Instructions make(OpType op, int first_operand, int second_operand);

int read_uint_8(Instructions ins, int offset);

int read_uint_16(Instructions ins, int offset);
Expand Down
59 changes: 59 additions & 0 deletions compiler/code.test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,25 @@ TEST_CASE("Test Make With One Byte Operand") {
}
}

TEST_CASE("Test Make With Two Byte And One Byte Operand") {
auto instruction = make(OpType::OpClosure, 65534, 255);
auto tt_expected = std::vector<Opcode>{as_opcode(OpType::OpClosure), 255, 254, 255};

if (instruction.size() != tt_expected.size()) {
std::cerr << "instruction has wrong length. want=" << tt_expected.size() << ", got=" << instruction.size()
<< std::endl;
}
REQUIRE(instruction.size() == tt_expected.size());

for (int i = 0; i < tt_expected.size(); i++) {
if (instruction.at(i) != tt_expected.at(i)) {
std::cerr << "wrong byte at pos " << i << ". want=" << (int) tt_expected.at(i) << ", got="
<< (int) instruction.at(i) << std::endl;
}
REQUIRE(instruction.at(i) == tt_expected.at(i));
}
}

TEST_CASE("Test Make No Operands") {
auto instruction = make(OpType::OpAdd);
auto tt_expected = std::vector<Opcode>{as_opcode(OpType::OpAdd)};
Expand All @@ -70,12 +89,14 @@ TEST_CASE("Test Instructions String") {
make(OpType::OpGetLocal, 1),
make(OpType::OpConstant, 2),
make(OpType::OpConstant, 65535),
make(OpType::OpClosure, 65535, 255),
};

std::string expected = R"(0000 OpAdd
0001 OpGetLocal 1
0003 OpConstant 2
0006 OpConstant 65535
0009 OpClosure 65535 255
)";

auto concatted = Instructions{};
Expand Down Expand Up @@ -123,3 +144,41 @@ TEST_CASE("Test Read Operands") {
REQUIRE(operands_read.at(0) == tt_operand);
}
}

TEST_CASE("Test Read Multiple Operands") {
std::vector<std::tuple<OpType, std::vector<int>, int>> tests = {
std::make_tuple(OpType::OpClosure, std::vector<int>{65535, 255}, 3),
};

for (const auto &tt: tests) {
const auto [tt_op, tt_operand, tt_bytes_read] = tt;

auto instruction = make(tt_op, tt_operand.at(0), tt_operand.at(1));

auto[def, ok] = lookup(tt_op);
if (!ok) {
std::cerr << "definition not found." << std::endl;
}
REQUIRE(ok);

Instructions slice = Instructions(instruction.begin() + 1, instruction.end());
auto[operands_read, n] = read_operands(def, slice);

if (n != tt_bytes_read) {
std::cerr << "n wrong. want=" << tt_bytes_read << ", got=" << n << std::endl;
}
REQUIRE(n == tt_bytes_read);

// Check first operand
if (operands_read.at(0) != tt_operand.at(0)) {
std::cerr << "operand wrong. want=" << tt_operand.at(0) << ", got=" << operands_read.at(0) << std::endl;
}
REQUIRE(operands_read.at(0) == tt_operand.at(0));

// Check second operand
if (operands_read.at(1) != tt_operand.at(1)) {
std::cerr << "operand wrong. want=" << tt_operand.at(1) << ", got=" << operands_read.at(1) << std::endl;
}
REQUIRE(operands_read.at(1) == tt_operand.at(1));
}
}
12 changes: 11 additions & 1 deletion compiler/compiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,8 @@ std::shared_ptr<Error> Compiler::compile(std::shared_ptr<Node> node)
compiled_fn->num_locals = num_locals;
compiled_fn->num_parameters = f->parameters.size();

emit(OpType::OpConstant, add_constant(compiled_fn));
auto fn_index = add_constant(compiled_fn);
emit(OpType::OpClosure, fn_index, 0);
// Return Statement
} else if (auto r = std::dynamic_pointer_cast<ReturnStatement>(node)) {
err = compile(r->return_value);
Expand Down Expand Up @@ -339,6 +340,15 @@ int Compiler::emit(OpType op, int operand) {
return pos;
}

int Compiler::emit(OpType op, int first_operand, int second_operand) {
auto ins = make(op, first_operand, second_operand);
auto pos = add_instruction(ins);

set_last_instruction(op, pos);

return pos;
}

int Compiler::add_instruction(Instructions ins) {
auto pos_new_instruction = static_cast<int>(scopes.at(scope_index).instructions.size());

Expand Down
2 changes: 2 additions & 0 deletions compiler/compiler.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ struct Compiler {

int emit(OpType op, int operand);

int emit(OpType op, int first_operand, int second_operand);

int add_instruction(Instructions ins);

void set_last_instruction(OpType op, int pos);
Expand Down
30 changes: 15 additions & 15 deletions compiler/compiler.test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -627,7 +627,7 @@ TEST_CASE("Test Function With Return") {
make(OpType::OpReturnValue),
};
std::vector<Instructions> expected_instructions = {
make(OpType::OpConstant, 2),
make(OpType::OpClosure, 2, 0),
make(OpType::OpPop),
};

Expand Down Expand Up @@ -664,7 +664,7 @@ TEST_CASE("Test Function With No Return") {
make(OpType::OpReturnValue),
};
std::vector<Instructions> expected_instructions = {
make(OpType::OpConstant, 2),
make(OpType::OpClosure, 2, 0),
make(OpType::OpPop),
};

Expand Down Expand Up @@ -701,7 +701,7 @@ TEST_CASE("Test Function With No Return And Two Expression Statements") {
make(OpType::OpReturnValue),
};
std::vector<Instructions> expected_instructions = {
make(OpType::OpConstant, 2),
make(OpType::OpClosure, 2, 0),
make(OpType::OpPop),
};

Expand Down Expand Up @@ -734,7 +734,7 @@ TEST_CASE("Test Function With No Return Value") {
make(OpType::OpReturn),
};
std::vector<Instructions> expected_instructions = {
make(OpType::OpConstant, 0),
make(OpType::OpClosure, 0, 0),
make(OpType::OpPop),
};

Expand Down Expand Up @@ -765,7 +765,7 @@ TEST_CASE("Test Function Calls") {
make(OpType::OpReturnValue),
};
std::vector<Instructions> expected_instructions = {
make(OpType::OpConstant, 1), // The compiled function
make(OpType::OpClosure, 1, 0), // The compiled function
make(OpType::OpCall, 0),
make(OpType::OpPop),
};
Expand Down Expand Up @@ -804,7 +804,7 @@ noArg();
make(OpType::OpReturnValue),
};
std::vector<Instructions> expected_instructions = {
make(OpType::OpConstant, 1), // The compiled function
make(OpType::OpClosure, 1, 0), // The compiled function
make(OpType::OpSetGlobal, 0),
make(OpType::OpGetGlobal, 0),
make(OpType::OpCall, 0),
Expand Down Expand Up @@ -837,7 +837,7 @@ noArg();
TEST_CASE("Test Let Statement Scopes 1") {
std::string input = R"(
let num = 55;
fn() { num };
fn() { num }
)";
std::vector<int> expected_integer_constants = std::vector<int>{55};
std::vector<Instructions> expected_fn_instructions = {
Expand All @@ -847,7 +847,7 @@ fn() { num };
std::vector<Instructions> expected_instructions = {
make(OpType::OpConstant, 0),
make(OpType::OpSetGlobal, 0),
make(OpType::OpConstant, 1),
make(OpType::OpClosure, 1, 0),
make(OpType::OpPop),
};

Expand Down Expand Up @@ -878,7 +878,7 @@ TEST_CASE("Test Let Statement Scopes 2") {
std::string input = R"(
fn() {
let num = 55;
num;
num
}
)";
std::vector<int> expected_integer_constants = std::vector<int>{55};
Expand All @@ -889,7 +889,7 @@ fn() {
make(OpType::OpReturnValue),
};
std::vector<Instructions> expected_instructions = {
make(OpType::OpConstant, 1),
make(OpType::OpClosure, 1, 0),
make(OpType::OpPop),
};

Expand Down Expand Up @@ -921,7 +921,7 @@ TEST_CASE("Test Let Statement Scopes 3") {
fn() {
let a = 55;
let b = 77;
a + b;
a + b
}
)";
std::vector<int> expected_integer_constants = std::vector<int>{55, 77};
Expand All @@ -936,7 +936,7 @@ fn() {
make(OpType::OpReturnValue),
};
std::vector<Instructions> expected_instructions = {
make(OpType::OpConstant, 2),
make(OpType::OpClosure, 2, 0),
make(OpType::OpPop),
};

Expand Down Expand Up @@ -974,7 +974,7 @@ oneArg(24);
make(OpType::OpReturnValue),
};
std::vector<Instructions> expected_instructions = {
make(OpType::OpConstant, 0),
make(OpType::OpClosure, 0, 0),
make(OpType::OpSetGlobal, 0),
make(OpType::OpGetGlobal, 0),
make(OpType::OpConstant, 1),
Expand Down Expand Up @@ -1020,7 +1020,7 @@ manyArg(24, 25, 26);
make(OpType::OpReturnValue),
};
std::vector<Instructions> expected_instructions = {
make(OpType::OpConstant, 0),
make(OpType::OpClosure, 0, 0),
make(OpType::OpSetGlobal, 0),
make(OpType::OpGetGlobal, 0),
make(OpType::OpConstant, 1),
Expand Down Expand Up @@ -1099,7 +1099,7 @@ TEST_CASE("Test Builtins Within Functions") {
make(OpType::OpReturnValue),
};
std::vector<Instructions> expected_instructions = {
make(OpType::OpConstant, 0),
make(OpType::OpClosure, 0, 0),
make(OpType::OpPop),
};

Expand Down
10 changes: 5 additions & 5 deletions compiler/frame.cpp
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
#include "frame.hpp"

Frame::Frame(std::shared_ptr<CompiledFunction> fn, int ip, int base_pointer) :
fn{fn}, ip{ip}, base_pointer{base_pointer} {}
Frame::Frame(std::shared_ptr<Closure> cl, int ip, int base_pointer) :
cl{cl}, ip{ip}, base_pointer{base_pointer} {}

Instructions Frame::instructions() {
return fn->instructions;
return cl->fn->instructions;
}

std::shared_ptr<Frame> new_frame(std::shared_ptr<CompiledFunction> fn, int base_pointer) {
return std::make_shared<Frame>(Frame(fn, -1, base_pointer));
std::shared_ptr<Frame> new_frame(std::shared_ptr<Closure> cl, int base_pointer) {
return std::make_shared<Frame>(Frame(cl, -1, base_pointer));
}
Loading

0 comments on commit ec50e2c

Please sign in to comment.