Compare commits

...

4 Commits

23 changed files with 486 additions and 100 deletions

View File

@ -104,7 +104,7 @@ void ast_binop::compile(const env_ptr& env, std::vector<instruction_ptr>& into)
right->compile(env, into); right->compile(env, into);
left->compile(env_ptr(new env_offset(1, env)), into); left->compile(env_ptr(new env_offset(1, env)), into);
into.push_back(instruction_ptr(new instruction_pushglobal(op_name(op)))); into.push_back(instruction_ptr(new instruction_pushglobal(op_action(op))));
into.push_back(instruction_ptr(new instruction_mkapp())); into.push_back(instruction_ptr(new instruction_mkapp()));
into.push_back(instruction_ptr(new instruction_mkapp())); into.push_back(instruction_ptr(new instruction_mkapp()));
} }

View File

@ -46,6 +46,7 @@ using branch_ptr = std::unique_ptr<branch>;
struct constructor { struct constructor {
std::string name; std::string name;
std::vector<std::string> types; std::vector<std::string> types;
int8_t tag;
constructor(std::string n, std::vector<std::string> ts) constructor(std::string n, std::vector<std::string> ts)
: name(std::move(n)), types(std::move(ts)) {} : name(std::move(n)), types(std::move(ts)) {}

View File

@ -48,6 +48,7 @@ void definition_defn::compile() {
} }
body->compile(new_env, instructions); body->compile(new_env, instructions);
instructions.push_back(instruction_ptr(new instruction_update(params.size()))); instructions.push_back(instruction_ptr(new instruction_update(params.size())));
instructions.push_back(instruction_ptr(new instruction_pop(params.size())));
} }
void definition_data::typecheck_first(type_mgr& mgr, type_env& env) { void definition_data::typecheck_first(type_mgr& mgr, type_env& env) {
@ -56,6 +57,7 @@ void definition_data::typecheck_first(type_mgr& mgr, type_env& env) {
int next_tag = 0; int next_tag = 0;
for(auto& constructor : constructors) { for(auto& constructor : constructors) {
constructor->tag = next_tag;
this_type->constructors[constructor->name] = { next_tag++ }; this_type->constructors[constructor->name] = { next_tag++ };
type_ptr full_type = return_type; type_ptr full_type = return_type;

View File

@ -19,6 +19,11 @@ void instruction_push::print(int indent, std::ostream& to) const {
to << "Push(" << offset << ")" << std::endl; to << "Push(" << offset << ")" << std::endl;
} }
void instruction_pop::print(int indent, std::ostream& to) const {
print_indent(indent, to);
to << "Pop(" << count << ")" << std::endl;
}
void instruction_mkapp::print(int indent, std::ostream& to) const { void instruction_mkapp::print(int indent, std::ostream& to) const {
print_indent(indent, to); print_indent(indent, to);
to << "MkApp()" << std::endl; to << "MkApp()" << std::endl;

View File

@ -41,6 +41,15 @@ struct instruction_push : public instruction {
void print(int indent, std::ostream& to) const; void print(int indent, std::ostream& to) const;
}; };
struct instruction_pop : public instruction {
int count;
instruction_pop(int c)
: count(c) {}
void print(int indent, std::ostream& to) const;
};
struct instruction_mkapp : public instruction { struct instruction_mkapp : public instruction {
void print(int indent, std::ostream& to) const; void print(int indent, std::ostream& to) const;
}; };

View File

@ -104,7 +104,7 @@ void ast_binop::compile(const env_ptr& env, std::vector<instruction_ptr>& into)
right->compile(env, into); right->compile(env, into);
left->compile(env_ptr(new env_offset(1, env)), into); left->compile(env_ptr(new env_offset(1, env)), into);
into.push_back(instruction_ptr(new instruction_pushglobal(op_name(op)))); into.push_back(instruction_ptr(new instruction_pushglobal(op_action(op))));
into.push_back(instruction_ptr(new instruction_mkapp())); into.push_back(instruction_ptr(new instruction_mkapp()));
into.push_back(instruction_ptr(new instruction_mkapp())); into.push_back(instruction_ptr(new instruction_mkapp()));
} }

View File

@ -46,6 +46,7 @@ using branch_ptr = std::unique_ptr<branch>;
struct constructor { struct constructor {
std::string name; std::string name;
std::vector<std::string> types; std::vector<std::string> types;
int8_t tag;
constructor(std::string n, std::vector<std::string> ts) constructor(std::string n, std::vector<std::string> ts)
: name(std::move(n)), types(std::move(ts)) {} : name(std::move(n)), types(std::move(ts)) {}

View File

@ -48,6 +48,7 @@ void definition_defn::compile() {
} }
body->compile(new_env, instructions); body->compile(new_env, instructions);
instructions.push_back(instruction_ptr(new instruction_update(params.size()))); instructions.push_back(instruction_ptr(new instruction_update(params.size())));
instructions.push_back(instruction_ptr(new instruction_pop(params.size())));
} }
void definition_data::typecheck_first(type_mgr& mgr, type_env& env) { void definition_data::typecheck_first(type_mgr& mgr, type_env& env) {
@ -56,6 +57,7 @@ void definition_data::typecheck_first(type_mgr& mgr, type_env& env) {
int next_tag = 0; int next_tag = 0;
for(auto& constructor : constructors) { for(auto& constructor : constructors) {
constructor->tag = next_tag;
this_type->constructors[constructor->name] = { next_tag++ }; this_type->constructors[constructor->name] = { next_tag++ };
type_ptr full_type = return_type; type_ptr full_type = return_type;

View File

@ -19,6 +19,11 @@ void instruction_push::print(int indent, std::ostream& to) const {
to << "Push(" << offset << ")" << std::endl; to << "Push(" << offset << ")" << std::endl;
} }
void instruction_pop::print(int indent, std::ostream& to) const {
print_indent(indent, to);
to << "Pop(" << count << ")" << std::endl;
}
void instruction_mkapp::print(int indent, std::ostream& to) const { void instruction_mkapp::print(int indent, std::ostream& to) const {
print_indent(indent, to); print_indent(indent, to);
to << "MkApp()" << std::endl; to << "MkApp()" << std::endl;

View File

@ -41,6 +41,15 @@ struct instruction_push : public instruction {
void print(int indent, std::ostream& to) const; void print(int indent, std::ostream& to) const;
}; };
struct instruction_pop : public instruction {
int count;
instruction_pop(int c)
: count(c) {}
void print(int indent, std::ostream& to) const;
};
struct instruction_mkapp : public instruction { struct instruction_mkapp : public instruction {
void print(int indent, std::ostream& to) const; void print(int indent, std::ostream& to) const;
}; };

View File

@ -1,5 +1,6 @@
#include "ast.hpp" #include "ast.hpp"
#include <ostream> #include <ostream>
#include "binop.hpp"
#include "error.hpp" #include "error.hpp"
static void print_indent(int n, std::ostream& to) { static void print_indent(int n, std::ostream& to) {
@ -104,7 +105,7 @@ void ast_binop::compile(const env_ptr& env, std::vector<instruction_ptr>& into)
right->compile(env, into); right->compile(env, into);
left->compile(env_ptr(new env_offset(1, env)), into); left->compile(env_ptr(new env_offset(1, env)), into);
into.push_back(instruction_ptr(new instruction_pushglobal(op_name(op)))); into.push_back(instruction_ptr(new instruction_pushglobal(op_action(op))));
into.push_back(instruction_ptr(new instruction_mkapp())); into.push_back(instruction_ptr(new instruction_mkapp()));
into.push_back(instruction_ptr(new instruction_mkapp())); into.push_back(instruction_ptr(new instruction_mkapp()));
} }

View File

@ -43,27 +43,6 @@ struct branch {
using branch_ptr = std::unique_ptr<branch>; using branch_ptr = std::unique_ptr<branch>;
struct constructor {
std::string name;
std::vector<std::string> types;
constructor(std::string n, std::vector<std::string> ts)
: name(std::move(n)), types(std::move(ts)) {}
};
using constructor_ptr = std::unique_ptr<constructor>;
struct definition {
virtual ~definition() = default;
virtual void typecheck_first(type_mgr& mgr, type_env& env) = 0;
virtual void typecheck_second(type_mgr& mgr, const type_env& env) const = 0;
virtual void resolve(const type_mgr& mgr) = 0;
virtual void compile() = 0;
};
using definition_ptr = std::unique_ptr<definition>;
struct ast_int : public ast { struct ast_int : public ast {
int value; int value;
@ -160,37 +139,3 @@ struct pattern_constr : public pattern {
void print(std::ostream &to) const; void print(std::ostream &to) const;
void match(type_ptr t, type_mgr&, type_env& env) const; void match(type_ptr t, type_mgr&, type_env& env) const;
}; };
struct definition_defn : public definition {
std::string name;
std::vector<std::string> params;
ast_ptr body;
type_ptr return_type;
std::vector<type_ptr> param_types;
std::vector<instruction_ptr> instructions;
definition_defn(std::string n, std::vector<std::string> p, ast_ptr b)
: name(std::move(n)), params(std::move(p)), body(std::move(b)) {
}
void typecheck_first(type_mgr& mgr, type_env& env);
void typecheck_second(type_mgr& mgr, const type_env& env) const;
void resolve(const type_mgr& mgr);
void compile();
};
struct definition_data : public definition {
std::string name;
std::vector<constructor_ptr> constructors;
definition_data(std::string n, std::vector<constructor_ptr> cs)
: name(std::move(n)), constructors(std::move(cs)) {}
void typecheck_first(type_mgr& mgr, type_env& env);
void typecheck_second(type_mgr& mgr, const type_env& env) const;
void resolve(const type_mgr& mgr);
void compile();
};

View File

@ -1,5 +1,10 @@
#include "ast.hpp" #include "definition.hpp"
#include "error.hpp" #include "error.hpp"
#include "ast.hpp"
#include "llvm_context.hpp"
#include <llvm/IR/DerivedTypes.h>
#include <llvm/IR/Function.h>
#include <llvm/IR/Type.h>
void definition_defn::typecheck_first(type_mgr& mgr, type_env& env) { void definition_defn::typecheck_first(type_mgr& mgr, type_env& env) {
return_type = mgr.new_type(); return_type = mgr.new_type();
@ -48,6 +53,18 @@ void definition_defn::compile() {
} }
body->compile(new_env, instructions); body->compile(new_env, instructions);
instructions.push_back(instruction_ptr(new instruction_update(params.size()))); instructions.push_back(instruction_ptr(new instruction_update(params.size())));
instructions.push_back(instruction_ptr(new instruction_pop(params.size())));
}
void definition_defn::gen_llvm_first(llvm_context& ctx) {
generated_function = ctx.create_custom_function(name, params.size());
}
void definition_defn::gen_llvm_second(llvm_context& ctx) {
ctx.builder.SetInsertPoint(&generated_function->getEntryBlock());
for(auto& instruction : instructions) {
instruction->gen_llvm(ctx, generated_function);
}
ctx.builder.CreateRetVoid();
} }
void definition_data::typecheck_first(type_mgr& mgr, type_env& env) { void definition_data::typecheck_first(type_mgr& mgr, type_env& env) {
@ -56,6 +73,7 @@ void definition_data::typecheck_first(type_mgr& mgr, type_env& env) {
int next_tag = 0; int next_tag = 0;
for(auto& constructor : constructors) { for(auto& constructor : constructors) {
constructor->tag = next_tag;
this_type->constructors[constructor->name] = { next_tag++ }; this_type->constructors[constructor->name] = { next_tag++ };
type_ptr full_type = return_type; type_ptr full_type = return_type;
@ -79,3 +97,20 @@ void definition_data::resolve(const type_mgr& mgr) {
void definition_data::compile() { void definition_data::compile() {
} }
void definition_data::gen_llvm_first(llvm_context& ctx) {
for(auto& constructor : constructors) {
auto new_function =
ctx.create_custom_function(constructor->name, constructor->types.size());
ctx.builder.SetInsertPoint(&new_function->getEntryBlock());
ctx.create_pack(new_function,
ctx.create_size(constructor->types.size()),
ctx.create_i8(constructor->tag)
);
ctx.builder.CreateRetVoid();
}
}
void definition_data::gen_llvm_second(llvm_context& ctx) {
// Nothing
}

View File

@ -0,0 +1,73 @@
#pragma once
#include <memory>
#include <vector>
#include "instruction.hpp"
#include "llvm_context.hpp"
#include "type_env.hpp"
struct ast;
using ast_ptr = std::unique_ptr<ast>;
struct definition {
virtual ~definition() = default;
virtual void typecheck_first(type_mgr& mgr, type_env& env) = 0;
virtual void typecheck_second(type_mgr& mgr, const type_env& env) const = 0;
virtual void resolve(const type_mgr& mgr) = 0;
virtual void compile() = 0;
virtual void gen_llvm_first(llvm_context& ctx) = 0;
virtual void gen_llvm_second(llvm_context& ctx) = 0;
};
using definition_ptr = std::unique_ptr<definition>;
struct constructor {
std::string name;
std::vector<std::string> types;
int8_t tag;
constructor(std::string n, std::vector<std::string> ts)
: name(std::move(n)), types(std::move(ts)) {}
};
using constructor_ptr = std::unique_ptr<constructor>;
struct definition_defn : public definition {
std::string name;
std::vector<std::string> params;
ast_ptr body;
type_ptr return_type;
std::vector<type_ptr> param_types;
std::vector<instruction_ptr> instructions;
llvm::Function* generated_function;
definition_defn(std::string n, std::vector<std::string> p, ast_ptr b)
: name(std::move(n)), params(std::move(p)), body(std::move(b)) {
}
void typecheck_first(type_mgr& mgr, type_env& env);
void typecheck_second(type_mgr& mgr, const type_env& env) const;
void resolve(const type_mgr& mgr);
void compile();
void gen_llvm_first(llvm_context& ctx);
void gen_llvm_second(llvm_context& ctx);
};
struct definition_data : public definition {
std::string name;
std::vector<constructor_ptr> constructors;
definition_data(std::string n, std::vector<constructor_ptr> cs)
: name(std::move(n)), constructors(std::move(cs)) {}
void typecheck_first(type_mgr& mgr, type_env& env);
void typecheck_second(type_mgr& mgr, const type_env& env) const;
void resolve(const type_mgr& mgr);
void compile();
void gen_llvm_first(llvm_context& ctx);
void gen_llvm_second(llvm_context& ctx);
};

View File

@ -1,5 +1,6 @@
#include "instruction.hpp" #include "instruction.hpp"
#include "llvm_context.hpp" #include "llvm_context.hpp"
#include <llvm/IR/BasicBlock.h>
#include <llvm/IR/Function.h> #include <llvm/IR/Function.h>
using namespace llvm; using namespace llvm;
@ -23,7 +24,9 @@ void instruction_pushglobal::print(int indent, std::ostream& to) const {
} }
void instruction_pushglobal::gen_llvm(llvm_context& ctx, Function* f) const { void instruction_pushglobal::gen_llvm(llvm_context& ctx, Function* f) const {
// TODO auto& global_f = ctx.custom_functions.at("f_" + name);
auto arity = ctx.create_i32(global_f->arity);
ctx.create_push(f, ctx.create_global(global_f->function, arity));
} }
void instruction_push::print(int indent, std::ostream& to) const { void instruction_push::print(int indent, std::ostream& to) const {
@ -35,6 +38,15 @@ void instruction_push::gen_llvm(llvm_context& ctx, Function* f) const {
ctx.create_push(f, ctx.create_peek(f, ctx.create_size(offset))); ctx.create_push(f, ctx.create_peek(f, ctx.create_size(offset)));
} }
void instruction_pop::print(int indent, std::ostream& to) const {
print_indent(indent, to);
to << "Pop(" << count << ")" << std::endl;
}
void instruction_pop::gen_llvm(llvm_context& ctx, Function* f) const {
ctx.create_popn(f, ctx.create_size(count));
}
void instruction_mkapp::print(int indent, std::ostream& to) const { void instruction_mkapp::print(int indent, std::ostream& to) const {
print_indent(indent, to); print_indent(indent, to);
to << "MkApp()" << std::endl; to << "MkApp()" << std::endl;
@ -87,7 +99,27 @@ void instruction_jump::print(int indent, std::ostream& to) const {
} }
void instruction_jump::gen_llvm(llvm_context& ctx, Function* f) const { void instruction_jump::gen_llvm(llvm_context& ctx, Function* f) const {
// TODO auto top_node = ctx.create_peek(f, ctx.create_size(0));
auto tag = ctx.unwrap_data_tag(top_node);
auto safety_block = BasicBlock::Create(ctx.ctx, "safety", f);
auto switch_op = ctx.builder.CreateSwitch(tag, safety_block, tag_mappings.size());
std::vector<BasicBlock*> blocks;
for(auto& branch : branches) {
auto branch_block = BasicBlock::Create(ctx.ctx, "branch", f);
ctx.builder.SetInsertPoint(branch_block);
for(auto& instruction : branch) {
instruction->gen_llvm(ctx, f);
}
ctx.builder.CreateBr(safety_block);
blocks.push_back(branch_block);
}
for(auto& mapping : tag_mappings) {
switch_op->addCase(ctx.create_i8(mapping.first), blocks[mapping.second]);
}
ctx.builder.SetInsertPoint(safety_block);
} }
void instruction_slide::print(int indent, std::ostream& to) const { void instruction_slide::print(int indent, std::ostream& to) const {

View File

@ -47,6 +47,16 @@ struct instruction_push : public instruction {
void gen_llvm(llvm_context& ctx, llvm::Function* f) const; void gen_llvm(llvm_context& ctx, llvm::Function* f) const;
}; };
struct instruction_pop : public instruction {
int count;
instruction_pop(int c)
: count(c) {}
void print(int indent, std::ostream& to) const;
void gen_llvm(llvm_context& ctx, llvm::Function* f) const;
};
struct instruction_mkapp : public instruction { struct instruction_mkapp : public instruction {
void print(int indent, std::ostream& to) const; void print(int indent, std::ostream& to) const;
void gen_llvm(llvm_context& ctx, llvm::Function* f) const; void gen_llvm(llvm_context& ctx, llvm::Function* f) const;

View File

@ -67,49 +67,49 @@ void llvm_context::create_functions() {
functions["stack_pop"] = Function::Create( functions["stack_pop"] = Function::Create(
FunctionType::get(node_ptr_type, { stack_ptr_type }, false), FunctionType::get(node_ptr_type, { stack_ptr_type }, false),
Function::LinkageTypes::ExternalLinkage, Function::LinkageTypes::ExternalLinkage,
"stack_push", "stack_pop",
&module &module
); );
functions["stack_peek"] = Function::Create( functions["stack_peek"] = Function::Create(
FunctionType::get(node_ptr_type, { stack_ptr_type, sizet_type }, false), FunctionType::get(node_ptr_type, { stack_ptr_type, sizet_type }, false),
Function::LinkageTypes::ExternalLinkage, Function::LinkageTypes::ExternalLinkage,
"stack_push", "stack_peek",
&module &module
); );
functions["stack_popn"] = Function::Create( functions["stack_popn"] = Function::Create(
FunctionType::get(void_type, { stack_ptr_type, sizet_type }, false), FunctionType::get(void_type, { stack_ptr_type, sizet_type }, false),
Function::LinkageTypes::ExternalLinkage, Function::LinkageTypes::ExternalLinkage,
"stack_push", "stack_popn",
&module &module
); );
functions["stack_slide"] = Function::Create( functions["stack_slide"] = Function::Create(
FunctionType::get(void_type, { stack_ptr_type, sizet_type }, false), FunctionType::get(void_type, { stack_ptr_type, sizet_type }, false),
Function::LinkageTypes::ExternalLinkage, Function::LinkageTypes::ExternalLinkage,
"stack_push", "stack_slide",
&module &module
); );
functions["stack_update"] = Function::Create( functions["stack_update"] = Function::Create(
FunctionType::get(void_type, { stack_ptr_type, sizet_type }, false), FunctionType::get(void_type, { stack_ptr_type, sizet_type }, false),
Function::LinkageTypes::ExternalLinkage, Function::LinkageTypes::ExternalLinkage,
"stack_push", "stack_update",
&module &module
); );
functions["stack_alloc"] = Function::Create( functions["stack_alloc"] = Function::Create(
FunctionType::get(void_type, { stack_ptr_type, sizet_type }, false), FunctionType::get(void_type, { stack_ptr_type, sizet_type }, false),
Function::LinkageTypes::ExternalLinkage, Function::LinkageTypes::ExternalLinkage,
"stack_push", "stack_alloc",
&module &module
); );
functions["stack_pack"] = Function::Create( functions["stack_pack"] = Function::Create(
FunctionType::get(void_type, { stack_ptr_type, sizet_type, tag_type }, false), FunctionType::get(void_type, { stack_ptr_type, sizet_type, tag_type }, false),
Function::LinkageTypes::ExternalLinkage, Function::LinkageTypes::ExternalLinkage,
"stack_push", "stack_pack",
&module &module
); );
functions["stack_split"] = Function::Create( functions["stack_split"] = Function::Create(
FunctionType::get(node_ptr_type, { stack_ptr_type, sizet_type }, false), FunctionType::get(node_ptr_type, { stack_ptr_type, sizet_type }, false),
Function::LinkageTypes::ExternalLinkage, Function::LinkageTypes::ExternalLinkage,
"stack_push", "stack_split",
&module &module
); );
@ -147,13 +147,13 @@ void llvm_context::create_functions() {
); );
} }
Value* llvm_context::create_i8(int8_t i) { ConstantInt* llvm_context::create_i8(int8_t i) {
return ConstantInt::get(ctx, APInt(8, i)); return ConstantInt::get(ctx, APInt(8, i));
} }
Value* llvm_context::create_i32(int32_t i) { ConstantInt* llvm_context::create_i32(int32_t i) {
return ConstantInt::get(ctx, APInt(32, i)); return ConstantInt::get(ctx, APInt(32, i));
} }
Value* llvm_context::create_size(size_t i) { ConstantInt* llvm_context::create_size(size_t i) {
return ConstantInt::get(ctx, APInt(sizeof(size_t) * 8, i)); return ConstantInt::get(ctx, APInt(sizeof(size_t) * 8, i));
} }
@ -202,8 +202,8 @@ Value* llvm_context::create_eval(Value* e) {
Value* llvm_context::unwrap_num(Value* v) { Value* llvm_context::unwrap_num(Value* v) {
auto num_ptr_type = PointerType::getUnqual(struct_types.at("node_num")); auto num_ptr_type = PointerType::getUnqual(struct_types.at("node_num"));
auto cast = builder.CreatePointerCast(v, num_ptr_type); auto cast = builder.CreatePointerCast(v, num_ptr_type);
auto offset_0 = create_size(0); auto offset_0 = create_i32(0);
auto offset_1 = create_size(1); auto offset_1 = create_i32(1);
auto int_ptr = builder.CreateGEP(cast, { offset_0, offset_1 }); auto int_ptr = builder.CreateGEP(cast, { offset_0, offset_1 });
return builder.CreateLoad(int_ptr); return builder.CreateLoad(int_ptr);
} }
@ -212,6 +212,15 @@ Value* llvm_context::create_num(Value* v) {
return builder.CreateCall(alloc_num_f, { v }); return builder.CreateCall(alloc_num_f, { v });
} }
Value* llvm_context::unwrap_data_tag(Value* v) {
auto data_ptr_type = PointerType::getUnqual(struct_types.at("node_data"));
auto cast = builder.CreatePointerCast(v, data_ptr_type);
auto offset_0 = create_i32(0);
auto offset_1 = create_i32(1);
auto tag_ptr = builder.CreateGEP(cast, { offset_0, offset_1 });
return builder.CreateLoad(tag_ptr);
}
Value* llvm_context::create_global(Value* f, Value* a) { Value* llvm_context::create_global(Value* f, Value* a) {
auto alloc_global_f = functions.at("alloc_global"); auto alloc_global_f = functions.at("alloc_global");
return builder.CreateCall(alloc_global_f, { f, a }); return builder.CreateCall(alloc_global_f, { f, a });
@ -221,3 +230,23 @@ Value* llvm_context::create_app(Value* l, Value* r) {
auto alloc_app_f = functions.at("alloc_app"); auto alloc_app_f = functions.at("alloc_app");
return builder.CreateCall(alloc_app_f, { l, r }); return builder.CreateCall(alloc_app_f, { l, r });
} }
llvm::Function* llvm_context::create_custom_function(std::string name, int32_t arity) {
auto void_type = llvm::Type::getVoidTy(ctx);
auto function_type =
llvm::FunctionType::get(void_type, { stack_ptr_type }, false);
auto new_function = llvm::Function::Create(
function_type,
llvm::Function::LinkageTypes::ExternalLinkage,
"f_" + name,
&module
);
auto start_block = llvm::BasicBlock::Create(ctx, "entry", new_function);
auto new_custom_f = custom_function_ptr(new custom_function());
new_custom_f->arity = arity;
new_custom_f->function = new_function;
custom_functions["f_" + name] = std::move(new_custom_f);
return new_function;
}

View File

@ -7,10 +7,18 @@
#include <map> #include <map>
struct llvm_context { struct llvm_context {
struct custom_function {
llvm::Function* function;
int32_t arity;
};
using custom_function_ptr = std::unique_ptr<custom_function>;
llvm::LLVMContext ctx; llvm::LLVMContext ctx;
llvm::IRBuilder<> builder; llvm::IRBuilder<> builder;
llvm::Module module; llvm::Module module;
std::map<std::string, custom_function_ptr> custom_functions;
std::map<std::string, llvm::Function*> functions; std::map<std::string, llvm::Function*> functions;
std::map<std::string, llvm::StructType*> struct_types; std::map<std::string, llvm::StructType*> struct_types;
@ -29,9 +37,9 @@ struct llvm_context {
void create_types(); void create_types();
void create_functions(); void create_functions();
llvm::Value* create_i8(int8_t); llvm::ConstantInt* create_i8(int8_t);
llvm::Value* create_i32(int32_t); llvm::ConstantInt* create_i32(int32_t);
llvm::Value* create_size(size_t); llvm::ConstantInt* create_size(size_t);
llvm::Value* create_pop(llvm::Function*); llvm::Value* create_pop(llvm::Function*);
llvm::Value* create_peek(llvm::Function*, llvm::Value*); llvm::Value* create_peek(llvm::Function*, llvm::Value*);
@ -48,7 +56,11 @@ struct llvm_context {
llvm::Value* unwrap_num(llvm::Value*); llvm::Value* unwrap_num(llvm::Value*);
llvm::Value* create_num(llvm::Value*); llvm::Value* create_num(llvm::Value*);
llvm::Value* unwrap_data_tag(llvm::Value*);
llvm::Value* create_global(llvm::Value*, llvm::Value*); llvm::Value* create_global(llvm::Value*, llvm::Value*);
llvm::Value* create_app(llvm::Value*, llvm::Value*); llvm::Value* create_app(llvm::Value*, llvm::Value*);
llvm::Function* create_custom_function(std::string name, int32_t arity);
}; };

View File

@ -1,8 +1,20 @@
#include "ast.hpp" #include "ast.hpp"
#include <iostream> #include <iostream>
#include "binop.hpp"
#include "definition.hpp"
#include "instruction.hpp"
#include "llvm_context.hpp"
#include "parser.hpp" #include "parser.hpp"
#include "error.hpp" #include "error.hpp"
#include "type.hpp" #include "type.hpp"
#include "llvm/IR/LegacyPassManager.h"
#include "llvm/IR/Verifier.h"
#include "llvm/Support/TargetSelect.h"
#include "llvm/Support/TargetRegistry.h"
#include "llvm/Support/raw_ostream.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Target/TargetOptions.h"
#include "llvm/Target/TargetMachine.h"
void yy::parser::error(const std::string& msg) { void yy::parser::error(const std::string& msg) {
std::cout << "An error occured: " << msg << std::endl; std::cout << "An error occured: " << msg << std::endl;
@ -55,6 +67,80 @@ void compile_program(const std::vector<definition_ptr>& prog) {
} }
} }
void gen_llvm_internal_op(llvm_context& ctx, binop op) {
auto new_function = ctx.create_custom_function(op_action(op), 2);
std::vector<instruction_ptr> instructions;
instructions.push_back(instruction_ptr(new instruction_push(1)));
instructions.push_back(instruction_ptr(new instruction_eval()));
instructions.push_back(instruction_ptr(new instruction_push(1)));
instructions.push_back(instruction_ptr(new instruction_eval()));
instructions.push_back(instruction_ptr(new instruction_binop(op)));
ctx.builder.SetInsertPoint(&new_function->getEntryBlock());
for(auto& instruction : instructions) {
instruction->gen_llvm(ctx, new_function);
}
ctx.builder.CreateRetVoid();
}
void output_llvm(llvm_context& ctx, const std::string& filename) {
std::string targetTriple = llvm::sys::getDefaultTargetTriple();
llvm::InitializeNativeTarget();
llvm::InitializeNativeTargetAsmParser();
llvm::InitializeNativeTargetAsmPrinter();
std::string error;
const llvm::Target* target =
llvm::TargetRegistry::lookupTarget(targetTriple, error);
if (!target) {
std::cerr << error << std::endl;
} else {
std::string cpu = "generic";
std::string features = "";
llvm::TargetOptions options;
llvm::TargetMachine* targetMachine =
target->createTargetMachine(targetTriple, cpu, features,
options, llvm::Optional<llvm::Reloc::Model>());
ctx.module.setDataLayout(targetMachine->createDataLayout());
ctx.module.setTargetTriple(targetTriple);
std::error_code ec;
llvm::raw_fd_ostream file(filename, ec, llvm::sys::fs::F_None);
if (ec) {
std::cerr << "Could not open output file: " << ec.message() << std::endl;
} else {
llvm::TargetMachine::CodeGenFileType type = llvm::TargetMachine::CGFT_ObjectFile;
llvm::legacy::PassManager pm;
if (targetMachine->addPassesToEmitFile(pm, file, NULL, type)) {
std::cerr << "Unable to emit target code" << std::endl;
} else {
pm.run(ctx.module);
file.close();
}
}
}
}
void gen_llvm(const std::vector<definition_ptr>& prog) {
llvm_context ctx;
gen_llvm_internal_op(ctx, PLUS);
gen_llvm_internal_op(ctx, MINUS);
gen_llvm_internal_op(ctx, TIMES);
gen_llvm_internal_op(ctx, DIVIDE);
for(auto& definition : prog) {
definition->gen_llvm_first(ctx);
}
for(auto& definition : prog) {
definition->gen_llvm_second(ctx);
}
llvm::verifyModule(ctx.module);
ctx.module.print(llvm::outs(), nullptr);
output_llvm(ctx, "program.o");
}
int main() { int main() {
yy::parser parser; yy::parser parser;
type_mgr mgr; type_mgr mgr;
@ -74,6 +160,7 @@ int main() {
try { try {
typecheck_program(program, mgr, env); typecheck_program(program, mgr, env);
compile_program(program); compile_program(program);
gen_llvm(program);
} catch(unification_error& err) { } catch(unification_error& err) {
std::cout << "failed to unify types: " << std::endl; std::cout << "failed to unify types: " << std::endl;
std::cout << " (1) \033[34m"; std::cout << " (1) \033[34m";

View File

@ -2,6 +2,7 @@
#include <string> #include <string>
#include <iostream> #include <iostream>
#include "ast.hpp" #include "ast.hpp"
#include "definition.hpp"
#include "parser.hpp" #include "parser.hpp"
std::vector<definition_ptr> program; std::vector<definition_ptr> program;

View File

@ -1,6 +1,7 @@
#include <stdint.h> #include <stdint.h>
#include <assert.h> #include <assert.h>
#include <memory.h> #include <memory.h>
#include <stdio.h>
#include "runtime.h" #include "runtime.h"
struct node_base* alloc_node() { struct node_base* alloc_node() {
@ -97,7 +98,7 @@ void stack_pack(struct stack* s, size_t n, int8_t t) {
struct node_base** data = malloc(sizeof(*data) * n); struct node_base** data = malloc(sizeof(*data) * n);
assert(data != NULL); assert(data != NULL);
memcpy(data, &s->data[s->count - 1 - n], n * sizeof(*data)); memcpy(data, &s->data[s->count - n], n * sizeof(*data));
struct node_data* new_node = (struct node_data*) alloc_node(); struct node_data* new_node = (struct node_data*) alloc_node();
new_node->array = data; new_node->array = data;
@ -153,7 +154,30 @@ struct node_base* eval(struct node_base* n) {
extern void f_main(struct stack* s); extern void f_main(struct stack* s);
void print_node(struct node_base* n) {
if(n->tag == NODE_APP) {
struct node_app* app = (struct node_app*) n;
print_node(app->left);
putchar(' ');
print_node(app->right);
} else if(n->tag == NODE_DATA) {
printf("(Packed)");
} else if(n->tag == NODE_GLOBAL) {
struct node_global* global = (struct node_global*) n;
printf("(Global: %p)", global->function);
} else if(n->tag == NODE_IND) {
print_node(((struct node_ind*) n)->next);
} else if(n->tag == NODE_NUM) {
struct node_num* num = (struct node_num*) n;
printf("%d", num->value);
}
}
int main(int argc, char** argv) { int main(int argc, char** argv) {
struct node_global* first_node = alloc_global(f_main, 0); struct node_global* first_node = alloc_global(f_main, 0);
struct node_base* result = eval((struct node_base*) first_node); struct node_base* result = eval((struct node_base*) first_node);
printf("Result: ");
print_node(result);
putchar('\n');
} }

View File

@ -3,6 +3,7 @@
%{ %{
#include <iostream> #include <iostream>
#include "ast.hpp" #include "ast.hpp"
#include "definition.hpp"
#include "parser.hpp" #include "parser.hpp"
#define YY_DECL yy::parser::symbol_type yylex() #define YY_DECL yy::parser::symbol_type yylex()

View File

@ -54,7 +54,6 @@ a `Module` object, which represents some collection of code and declarations
{{< codeblock "C++" "compiler/08/llvm_context.hpp" >}} {{< codeblock "C++" "compiler/08/llvm_context.hpp" >}}
{{< todo >}} Consistently name context / state.{{< /todo >}}
{{< todo >}} Explain creation functions. {{< /todo >}} {{< todo >}} Explain creation functions. {{< /todo >}}
We include the LLVM context, builder, and module as members We include the LLVM context, builder, and module as members
@ -82,35 +81,58 @@ an `llvm::LinkageType`, the name of the function, and the module
in which the function is declared. Since we only have one in which the function is declared. Since we only have one
module (the one we initialized in the constructor) that's module (the one we initialized in the constructor) that's
the module we pass in. The name of the function is the same the module we pass in. The name of the function is the same
as its name in the runtime, and the linkage type is always as its name in the runtime. The linkage type is a little
external. The only remaining parameter is more complicated - it tells LLVM the "visibility" of a function.
the `llvm::FunctionType`, which is created using code like: "Private" or "Internal" would hide this function from the linker
(like `static` functions in C). However, we want to do the opposite: our
generated functions should be accessible from other code.
Thus, our linkage type is "External".
{{< todo >}} Why external? {{< /todo >}} The only remaining parameter is the `llvm::FunctionType`, which
is created using code like:
```C++ ```C++
llvm::FunctionType::get(return_type, {param_type_1, param_type_2, ...}, is_variadic) llvm::FunctionType::get(return_type, {param_type_1, param_type_2, ...}, is_variadic)
``` ```
Declaring all the functions and types in our runtime is mostly Declaring all the functions and types in our runtime is mostly
just tedious. Here are a few lines from `create_types()`, from just tedious. Here are a few lines from `create_functions()`, which
give a very good idea of the rest of that method:
{{< codelines "C++" "compiler/08/llvm_context.cpp" 47 60 >}}
Similarly, here are a few lines from `create_types()`, from
which you can extrapolate the rest: which you can extrapolate the rest:
{{< codelines "C++" "compiler/08/llvm_context.cpp" 7 11 >}} {{< codelines "C++" "compiler/08/llvm_context.cpp" 7 11 >}}
{{< todo >}} Also show struct body setters. {{< /todo >}} We also tell LLVM the contents of our structs, so that
we may later reference specific fields. This is just like
forward declaration - we can forward declare a struct
in C/C++, but unless we also declare its contents,
we can't access what's inside. Below is the code
for specifying the body of `node_base` and `node_app`.
Similarly, here are a few lines from `create_functions()`, which {{< codelines "C++" "compiler/08/llvm_context.cpp" 19 26 >}}
give a very good idea of the rest of that method:
{{< codelines "C++" "compiler/08/llvm_context.cpp" 20 27 >}} There's still more functionality packed into `llvm_context`.
Let's next take a look into `custom_function`, and
the `create_custom_function` method. Why do we need
these?
This completes our implementation of the context. This isn't the end of our `llvm_context` class: it also
has a variety of `create_*` methods! Let's take a look
at their signatures. Most return either `void`,
`llvm::ConstantInt*`, or `llvm::Value*`. Since
`llvm::ConstantInt*` is a subclass of `llvm::Value*`, let's
just treat it as simply an `llvm::Value*` while trying
to understand these methods.
So, what is `llvm::Value`? To answer this question, let's
first understand how the LLVM IR works.
### LLVM IR ### LLVM IR
It's now time to look at generating actual code for each G-machine instruction. An important property of LLVM IR is that it is in __Single Static Assignment__
Before we do this, we need to get a little bit of an understanding of what LLVM
IR is like. An important property of LLVM IR is that it is in __Single Static Assignment__
(SSA) form. This means that each variable can only be assigned to once. For instance, (SSA) form. This means that each variable can only be assigned to once. For instance,
if we use `<-` to represent assignment, the following program is valid: if we use `<-` to represent assignment, the following program is valid:
@ -140,13 +162,26 @@ x2 <- x1 + 1
In practice, LLVM's C++ API can take care of versioning variables on its own, by In practice, LLVM's C++ API can take care of versioning variables on its own, by
auto-incrementing numbers associated with each variable we use. auto-incrementing numbers associated with each variable we use.
We need not get too deep into the specifics of LLVM IR's textual Assigned to each variable is `llvm::Value`. The LLVM documentation states:
representation, since we will largely be working with the C++
API to interact with it. We do, however, need to understand one more > It is the base class of all values computed by a program that may be used as operands to other values.
concept from the world of compiler design: __basic blocks__. A basic
block is a sequence of instructions that are guaranteed to be executed It's important to understand that `llvm::Value` __does not store the result of the computation__.
one after another. This means that a basic block cannot have It rather represents how something may be computed. 1 is a value because it computed by
an if/else, jump, or any other type of control flow anywhere just returning. `x + 1` is a value because it is computed by adding the value inside of
`x` to 1. Since we cannot modify a variable once we've declared it, we will
keep assigning intermediate results to new variables, constructing new values
out of values that we've already specified.
This somewhat elucidates what the `create_*` functions do: `create_i8` creates an 8-bit integer
value, and `create_pop` creates a value that is computed by calling
our runtime `stack_pop` function.
Before we move on to look at the implementations of these functions,
we need to understand another concept from the world of compiler design:
__basic blocks__. A basic block is a sequence of instructions that
are guaranteed to be executed one after another. This means that a
basic block cannot have an if/else, jump, or any other type of control flow anywhere
except at the end. If control flow could appear inside the basic block, except at the end. If control flow could appear inside the basic block,
there would be opporunity for execution of some, but not all, there would be opporunity for execution of some, but not all,
instructions in the block, violating the definition. Every time instructions in the block, violating the definition. Every time
@ -155,7 +190,74 @@ Writing control flow involves creating several blocks, with each
block serving as the destination of a potential jump. We will block serving as the destination of a potential jump. We will
see this used to compile the Jump instruction. see this used to compile the Jump instruction.
### Generating LLVM ### Generating LLVM IR
Now that we understand what `llvm::Value` is, and have a vague
understanding of how LLVM is structured, let's take a look at
the implementations of the `create_*` functions. The simplest
is `create_i8`:
{{< codelines "C++" "compiler/08/llvm_context.cpp" 150 152 >}}
Not much to see here. We create an instance of the `llvm::ConstantInt` class,
from the actual integer given to the method. As we said before,
`llvm::ConstantInt` is a subclass of `llvm::Value`. Next up, let's look
at `create_pop`:
{{< codelines "C++" "compiler/08/llvm_context.cpp" 160 163 >}}
We first retrieve an `llvm::Function` associated with `stack_pop`
from our map, and then use `llvm::IRBuilder::CreateCall` to insert
a value that represents a function call into the currently
selected basic block (the builder's state is what
dictates what the "selected basic block" is). `CreateCall`
takes as parameters the function we want to call (`stack_pop`,
which we store into the `pop_f` variable), as well as the arguments
to the function (for which we pass `f->arg_begin()`).
Hold on. What the heck is `arg_begin()`? Why do we take a function
as a paramter to this method? The answer is fairly simple: this
method is used when we are
generating a function with signature `void f_(struct stack* s)`
(we discussed the signature in the previous post). The
parameter that we give to `create_pop` is this function we're
generating, and `arg_begin()` gets the value that represents
the first parameter to our function - `s`! Since `stack_pop`
takes a stack, we need to give it the stack we're working on,
and so we use `f->arg_begin()` to access it.
Most of the other functions follow this exact pattern, with small
deviations. However, another function uses a more complicated LLVM
instruction:
{{< codelines "C++" "compiler/08/llvm_context.cpp" 202 209 >}}
`unwrap_num` is used to cast a given node pointer to a pointer
to a number node, and then return the integer value from
that number node. It starts fairly innocently: we ask
LLVM for the type of a pointer to a `node_num` struct,
and then use `CreatePointerCast` to create a value
that is the same node pointer we're given, but now interpreted
as a number node pointer. We now have to access
the `value` field of our node. `CreateGEP` helps us with
this: given a pointer to a node, and two offsets
`n` and `k`, it effectively performs the following:
```C++
&(num_pointer[n]->kth_field)
```
The first offset, then, gives an index into the "array"
represented by the pointer, while the second offset
gives the index of the field we want to access. We
want to dereference the pointer (`num_pointer[0]`),
and we want the second field (`1`, when counting from 0).
Thus, we call CreateGEP with these offsets and our pointers.
This still leaves us with a pointer to a number, rather
than the number itself. To dereference the pointer, we use
`CreateLoad`. This gives us the value of the number node,
which we promptly return.
Let's envision a `gen_llvm` method on the `instruction` struct. Let's envision a `gen_llvm` method on the `instruction` struct.
We need access to all the other functions from our runtime, We need access to all the other functions from our runtime,
such as `stack_init`, and functions from our program such such as `stack_init`, and functions from our program such