Compare commits
4 Commits
c79b5a4120
...
1a8a1c3052
Author | SHA1 | Date | |
---|---|---|---|
1a8a1c3052 | |||
2994f8983d | |||
64227f2873 | |||
9aef499deb |
|
@ -104,7 +104,7 @@ void ast_binop::compile(const env_ptr& env, std::vector<instruction_ptr>& into)
|
|||
right->compile(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()));
|
||||
}
|
||||
|
|
|
@ -46,6 +46,7 @@ using branch_ptr = std::unique_ptr<branch>;
|
|||
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)) {}
|
||||
|
|
|
@ -48,6 +48,7 @@ void definition_defn::compile() {
|
|||
}
|
||||
body->compile(new_env, instructions);
|
||||
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) {
|
||||
|
@ -56,6 +57,7 @@ void definition_data::typecheck_first(type_mgr& mgr, type_env& env) {
|
|||
int next_tag = 0;
|
||||
|
||||
for(auto& constructor : constructors) {
|
||||
constructor->tag = next_tag;
|
||||
this_type->constructors[constructor->name] = { next_tag++ };
|
||||
|
||||
type_ptr full_type = return_type;
|
||||
|
|
|
@ -19,6 +19,11 @@ void instruction_push::print(int indent, std::ostream& to) const {
|
|||
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 {
|
||||
print_indent(indent, to);
|
||||
to << "MkApp()" << std::endl;
|
||||
|
|
|
@ -41,6 +41,15 @@ struct instruction_push : public instruction {
|
|||
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 {
|
||||
void print(int indent, std::ostream& to) const;
|
||||
};
|
||||
|
|
|
@ -104,7 +104,7 @@ void ast_binop::compile(const env_ptr& env, std::vector<instruction_ptr>& into)
|
|||
right->compile(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()));
|
||||
}
|
||||
|
|
|
@ -46,6 +46,7 @@ using branch_ptr = std::unique_ptr<branch>;
|
|||
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)) {}
|
||||
|
|
|
@ -48,6 +48,7 @@ void definition_defn::compile() {
|
|||
}
|
||||
body->compile(new_env, instructions);
|
||||
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) {
|
||||
|
@ -56,6 +57,7 @@ void definition_data::typecheck_first(type_mgr& mgr, type_env& env) {
|
|||
int next_tag = 0;
|
||||
|
||||
for(auto& constructor : constructors) {
|
||||
constructor->tag = next_tag;
|
||||
this_type->constructors[constructor->name] = { next_tag++ };
|
||||
|
||||
type_ptr full_type = return_type;
|
||||
|
|
|
@ -19,6 +19,11 @@ void instruction_push::print(int indent, std::ostream& to) const {
|
|||
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 {
|
||||
print_indent(indent, to);
|
||||
to << "MkApp()" << std::endl;
|
||||
|
|
|
@ -41,6 +41,15 @@ struct instruction_push : public instruction {
|
|||
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 {
|
||||
void print(int indent, std::ostream& to) const;
|
||||
};
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#include "ast.hpp"
|
||||
#include <ostream>
|
||||
#include "binop.hpp"
|
||||
#include "error.hpp"
|
||||
|
||||
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);
|
||||
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()));
|
||||
}
|
||||
|
|
|
@ -43,27 +43,6 @@ struct 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 {
|
||||
int value;
|
||||
|
||||
|
@ -160,37 +139,3 @@ struct pattern_constr : public pattern {
|
|||
void print(std::ostream &to) 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();
|
||||
};
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
#include "ast.hpp"
|
||||
#include "definition.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) {
|
||||
return_type = mgr.new_type();
|
||||
|
@ -48,6 +53,18 @@ void definition_defn::compile() {
|
|||
}
|
||||
body->compile(new_env, instructions);
|
||||
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) {
|
||||
|
@ -56,6 +73,7 @@ void definition_data::typecheck_first(type_mgr& mgr, type_env& env) {
|
|||
int next_tag = 0;
|
||||
|
||||
for(auto& constructor : constructors) {
|
||||
constructor->tag = next_tag;
|
||||
this_type->constructors[constructor->name] = { next_tag++ };
|
||||
|
||||
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::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
|
||||
}
|
||||
|
|
73
code/compiler/08/definition.hpp
Normal file
73
code/compiler/08/definition.hpp
Normal 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);
|
||||
};
|
|
@ -1,5 +1,6 @@
|
|||
#include "instruction.hpp"
|
||||
#include "llvm_context.hpp"
|
||||
#include <llvm/IR/BasicBlock.h>
|
||||
#include <llvm/IR/Function.h>
|
||||
|
||||
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 {
|
||||
// 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 {
|
||||
|
@ -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)));
|
||||
}
|
||||
|
||||
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 {
|
||||
print_indent(indent, to);
|
||||
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 {
|
||||
// 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 {
|
||||
|
|
|
@ -47,6 +47,16 @@ struct instruction_push : public instruction {
|
|||
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 {
|
||||
void print(int indent, std::ostream& to) const;
|
||||
void gen_llvm(llvm_context& ctx, llvm::Function* f) const;
|
||||
|
|
|
@ -67,49 +67,49 @@ void llvm_context::create_functions() {
|
|||
functions["stack_pop"] = Function::Create(
|
||||
FunctionType::get(node_ptr_type, { stack_ptr_type }, false),
|
||||
Function::LinkageTypes::ExternalLinkage,
|
||||
"stack_push",
|
||||
"stack_pop",
|
||||
&module
|
||||
);
|
||||
functions["stack_peek"] = Function::Create(
|
||||
FunctionType::get(node_ptr_type, { stack_ptr_type, sizet_type }, false),
|
||||
Function::LinkageTypes::ExternalLinkage,
|
||||
"stack_push",
|
||||
"stack_peek",
|
||||
&module
|
||||
);
|
||||
functions["stack_popn"] = Function::Create(
|
||||
FunctionType::get(void_type, { stack_ptr_type, sizet_type }, false),
|
||||
Function::LinkageTypes::ExternalLinkage,
|
||||
"stack_push",
|
||||
"stack_popn",
|
||||
&module
|
||||
);
|
||||
functions["stack_slide"] = Function::Create(
|
||||
FunctionType::get(void_type, { stack_ptr_type, sizet_type }, false),
|
||||
Function::LinkageTypes::ExternalLinkage,
|
||||
"stack_push",
|
||||
"stack_slide",
|
||||
&module
|
||||
);
|
||||
functions["stack_update"] = Function::Create(
|
||||
FunctionType::get(void_type, { stack_ptr_type, sizet_type }, false),
|
||||
Function::LinkageTypes::ExternalLinkage,
|
||||
"stack_push",
|
||||
"stack_update",
|
||||
&module
|
||||
);
|
||||
functions["stack_alloc"] = Function::Create(
|
||||
FunctionType::get(void_type, { stack_ptr_type, sizet_type }, false),
|
||||
Function::LinkageTypes::ExternalLinkage,
|
||||
"stack_push",
|
||||
"stack_alloc",
|
||||
&module
|
||||
);
|
||||
functions["stack_pack"] = Function::Create(
|
||||
FunctionType::get(void_type, { stack_ptr_type, sizet_type, tag_type }, false),
|
||||
Function::LinkageTypes::ExternalLinkage,
|
||||
"stack_push",
|
||||
"stack_pack",
|
||||
&module
|
||||
);
|
||||
functions["stack_split"] = Function::Create(
|
||||
FunctionType::get(node_ptr_type, { stack_ptr_type, sizet_type }, false),
|
||||
Function::LinkageTypes::ExternalLinkage,
|
||||
"stack_push",
|
||||
"stack_split",
|
||||
&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));
|
||||
}
|
||||
Value* llvm_context::create_i32(int32_t i) {
|
||||
ConstantInt* llvm_context::create_i32(int32_t 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));
|
||||
}
|
||||
|
||||
|
@ -202,8 +202,8 @@ Value* llvm_context::create_eval(Value* e) {
|
|||
Value* llvm_context::unwrap_num(Value* v) {
|
||||
auto num_ptr_type = PointerType::getUnqual(struct_types.at("node_num"));
|
||||
auto cast = builder.CreatePointerCast(v, num_ptr_type);
|
||||
auto offset_0 = create_size(0);
|
||||
auto offset_1 = create_size(1);
|
||||
auto offset_0 = create_i32(0);
|
||||
auto offset_1 = create_i32(1);
|
||||
auto int_ptr = builder.CreateGEP(cast, { offset_0, offset_1 });
|
||||
return builder.CreateLoad(int_ptr);
|
||||
}
|
||||
|
@ -212,6 +212,15 @@ Value* llvm_context::create_num(Value* 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) {
|
||||
auto alloc_global_f = functions.at("alloc_global");
|
||||
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");
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -7,10 +7,18 @@
|
|||
#include <map>
|
||||
|
||||
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::IRBuilder<> builder;
|
||||
llvm::Module module;
|
||||
|
||||
std::map<std::string, custom_function_ptr> custom_functions;
|
||||
std::map<std::string, llvm::Function*> functions;
|
||||
std::map<std::string, llvm::StructType*> struct_types;
|
||||
|
||||
|
@ -29,9 +37,9 @@ struct llvm_context {
|
|||
void create_types();
|
||||
void create_functions();
|
||||
|
||||
llvm::Value* create_i8(int8_t);
|
||||
llvm::Value* create_i32(int32_t);
|
||||
llvm::Value* create_size(size_t);
|
||||
llvm::ConstantInt* create_i8(int8_t);
|
||||
llvm::ConstantInt* create_i32(int32_t);
|
||||
llvm::ConstantInt* create_size(size_t);
|
||||
|
||||
llvm::Value* create_pop(llvm::Function*);
|
||||
llvm::Value* create_peek(llvm::Function*, llvm::Value*);
|
||||
|
@ -48,7 +56,11 @@ struct llvm_context {
|
|||
llvm::Value* unwrap_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_app(llvm::Value*, llvm::Value*);
|
||||
|
||||
llvm::Function* create_custom_function(std::string name, int32_t arity);
|
||||
};
|
||||
|
|
|
@ -1,8 +1,20 @@
|
|||
#include "ast.hpp"
|
||||
#include <iostream>
|
||||
#include "binop.hpp"
|
||||
#include "definition.hpp"
|
||||
#include "instruction.hpp"
|
||||
#include "llvm_context.hpp"
|
||||
#include "parser.hpp"
|
||||
#include "error.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) {
|
||||
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() {
|
||||
yy::parser parser;
|
||||
type_mgr mgr;
|
||||
|
@ -74,6 +160,7 @@ int main() {
|
|||
try {
|
||||
typecheck_program(program, mgr, env);
|
||||
compile_program(program);
|
||||
gen_llvm(program);
|
||||
} catch(unification_error& err) {
|
||||
std::cout << "failed to unify types: " << std::endl;
|
||||
std::cout << " (1) \033[34m";
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
#include <string>
|
||||
#include <iostream>
|
||||
#include "ast.hpp"
|
||||
#include "definition.hpp"
|
||||
#include "parser.hpp"
|
||||
|
||||
std::vector<definition_ptr> program;
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#include <stdint.h>
|
||||
#include <assert.h>
|
||||
#include <memory.h>
|
||||
#include <stdio.h>
|
||||
#include "runtime.h"
|
||||
|
||||
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);
|
||||
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();
|
||||
new_node->array = data;
|
||||
|
@ -153,7 +154,30 @@ struct node_base* eval(struct node_base* n) {
|
|||
|
||||
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) {
|
||||
struct node_global* first_node = alloc_global(f_main, 0);
|
||||
struct node_base* result = eval((struct node_base*) first_node);
|
||||
|
||||
printf("Result: ");
|
||||
print_node(result);
|
||||
putchar('\n');
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
%{
|
||||
#include <iostream>
|
||||
#include "ast.hpp"
|
||||
#include "definition.hpp"
|
||||
#include "parser.hpp"
|
||||
|
||||
#define YY_DECL yy::parser::symbol_type yylex()
|
||||
|
|
|
@ -54,7 +54,6 @@ a `Module` object, which represents some collection of code and declarations
|
|||
|
||||
{{< codeblock "C++" "compiler/08/llvm_context.hpp" >}}
|
||||
|
||||
{{< todo >}} Consistently name context / state.{{< /todo >}}
|
||||
{{< todo >}} Explain creation functions. {{< /todo >}}
|
||||
|
||||
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
|
||||
module (the one we initialized in the constructor) that's
|
||||
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
|
||||
external. The only remaining parameter is
|
||||
the `llvm::FunctionType`, which is created using code like:
|
||||
as its name in the runtime. The linkage type is a little
|
||||
more complicated - it tells LLVM the "visibility" of a function.
|
||||
"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++
|
||||
llvm::FunctionType::get(return_type, {param_type_1, param_type_2, ...}, is_variadic)
|
||||
```
|
||||
|
||||
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:
|
||||
|
||||
{{< 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
|
||||
give a very good idea of the rest of that method:
|
||||
{{< codelines "C++" "compiler/08/llvm_context.cpp" 19 26 >}}
|
||||
|
||||
{{< 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
|
||||
It's now time to look at generating actual code for each G-machine instruction.
|
||||
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__
|
||||
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,
|
||||
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
|
||||
auto-incrementing numbers associated with each variable we use.
|
||||
|
||||
We need not get too deep into the specifics of LLVM IR's textual
|
||||
representation, since we will largely be working with the C++
|
||||
API to interact with it. We do, however, need to understand one more
|
||||
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
|
||||
Assigned to each variable is `llvm::Value`. The LLVM documentation states:
|
||||
|
||||
> It is the base class of all values computed by a program that may be used as operands to other values.
|
||||
|
||||
It's important to understand that `llvm::Value` __does not store the result of the computation__.
|
||||
It rather represents how something may be computed. 1 is a value because it computed by
|
||||
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,
|
||||
there would be opporunity for execution of some, but not all,
|
||||
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
|
||||
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.
|
||||
We need access to all the other functions from our runtime,
|
||||
such as `stack_init`, and functions from our program such
|
||||
|
|
Loading…
Reference in New Issue
Block a user