From c8b0cfd928abd52eb913cf1fad1f4bd9c10123d1 Mon Sep 17 00:00:00 2001 From: Maxwell Jeffress Date: Fri, 7 Aug 2026 17:35:48 +1000 Subject: [PATCH] continue working on ir --- meson.build | 1 + src/ir/ir.hpp | 59 ++++++++++++++++++ src/ir/irbuilder.cpp | 65 ++++++++++++++++++++ src/ir/irbuilder.hpp | 43 ++++++++++++++ src/main.cpp | 9 +++ src/parser/parser.hpp | 2 + src/typechecker/typechecker.cpp | 102 ++++++++++++++++---------------- src/typechecker/typechecker.hpp | 18 ++++-- 8 files changed, 243 insertions(+), 56 deletions(-) create mode 100644 src/ir/irbuilder.cpp create mode 100644 src/ir/irbuilder.hpp diff --git a/meson.build b/meson.build index 9fde0ae..015afb9 100644 --- a/meson.build +++ b/meson.build @@ -7,6 +7,7 @@ sources = files( 'src/typechecker/typechecker.cpp', 'src/typechecker/type.cpp', 'src/ir/ir.cpp', + 'src/ir/irbuilder.cpp', 'src/compiler/compiler.cpp' ) diff --git a/src/ir/ir.hpp b/src/ir/ir.hpp index e69de29..1abbd5f 100644 --- a/src/ir/ir.hpp +++ b/src/ir/ir.hpp @@ -0,0 +1,59 @@ +#pragma once + +#include +#include +#include +#include + +#include "../lexer/lexer.hpp" + +namespace Solstice { + + enum class InstructionType { + Jump, JumpIfTrue, JumpIfFalse, Return, + Call, + }; + + enum class ArgumentType { + None, Register, Function, Literal + }; + + class Argument { + + std::variant value; + + public: + ArgumentType type = ArgumentType::None; + + std::optional getRegister(); + std::optional getString(); + std::optional getLiteral(); + + Argument() : type(ArgumentType::None) {} + Argument(ArgumentType type) : type(type) {} + + Argument(int64_t value) : type(ArgumentType::Register), value(value) {} + Argument(std::string value) : type(ArgumentType::Function), value(value) {} + Argument(Literal value) : type(ArgumentType::Literal), value(value) {} + + }; + + struct Instruction { + uint64_t tmpId; + std::vector args; + InstructionType type; + }; + + struct BasicBlock { + std::vector instructions; + }; + + struct IRFunction { + std::vector blocks; + }; + + struct Program { + std::vector functions; + }; + +} diff --git a/src/ir/irbuilder.cpp b/src/ir/irbuilder.cpp new file mode 100644 index 0000000..2ce15b4 --- /dev/null +++ b/src/ir/irbuilder.cpp @@ -0,0 +1,65 @@ +#include "irbuilder.hpp" +#include "ir.hpp" +#include + +namespace Solstice { + + void IRBuilder::buildNode(Node& node) { + switch (node.type) { + case NodeType::Root: + buildRootNode(node); + break; + case NodeType::Literal: + buildLiteralNode(node); + break; + case NodeType::Identifier: + buildIdentifierNode(node); + break; + case NodeType::FunctionBind: + buildFunctionBindNode(node); + break; + } + } + + void IRBuilder::buildLiteralNode(Node& node) { + node.arg = *node.getLiteral(); + } + + void IRBuilder::buildIdentifierNode(Node& node) { + if (currentContext == nullptr) { + throw std::runtime_error("current context does not exist"); + } + if (currentContext->variables.find(*node.getIdentifier()) == currentContext->variables.end()) { + throw std::runtime_error("identifier has not been assigned a virtual register"); + } + node.arg = currentContext->variables[*node.getIdentifier()]; + } + + void IRBuilder::buildRootNode(Node& node) { + for (auto& child : node.children) { + buildNode(child); + } + } + + void IRBuilder::buildFunctionBindNode(Node& node) { + IRContext context; + currentContext = &context; + + IRFunction function; + currentFunction = &function; + + // allocate one block, which is our start block + currentFunction->blocks.emplace_back(); + currentBlock = ¤tFunction->blocks[0]; + } + + void IRBuilder::build() { + buildNode(in); + } + + void IRBuilder::optimise() {} + + Program& IRBuilder::getProgram() { + return out; + } +} diff --git a/src/ir/irbuilder.hpp b/src/ir/irbuilder.hpp new file mode 100644 index 0000000..05bb69c --- /dev/null +++ b/src/ir/irbuilder.hpp @@ -0,0 +1,43 @@ +#pragma once + +#include "ir.hpp" +#include "../parser/parser.hpp" +#include "../typechecker/typechecker.hpp" +#include +#include + +namespace Solstice { + + struct IRContext { + std::unordered_map variables; + }; + + class IRBuilder { + Node in; + Program out; + + Context tcContext; + + IRContext* currentContext = nullptr; + + BasicBlock* currentBlock = nullptr; + IRFunction* currentFunction = nullptr; + + void buildNode(Node& node); + + void buildRootNode(Node& node); + void buildLiteralNode(Node& node); + void buildIdentifierNode(Node& node); + + void buildFunctionBindNode(Node& node); + + public: + IRBuilder() = delete; + IRBuilder(const Node& node, const Context& context) : in(node), tcContext(context) {} + + void build(); + void optimise(); + + Program& getProgram(); + }; +} diff --git a/src/main.cpp b/src/main.cpp index 923c105..0b6c50f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,3 +1,4 @@ +#include "ir/irbuilder.hpp" #include "lexer/lexer.hpp" #include "parser/parser.hpp" #include "typechecker/typechecker.hpp" @@ -27,5 +28,13 @@ int main(int argc, char** argv) { Solstice::TypeChecker typeChecker(parsed); typeChecker.checkTypes(); + Solstice::Context& context = typeChecker.getContext(); + + Solstice::IRBuilder irBuilder(parsed, context); + irBuilder.build(); + irBuilder.optimise(); + + Solstice::Program& program = irBuilder.getProgram(); + return 0; } diff --git a/src/parser/parser.hpp b/src/parser/parser.hpp index 32aac1c..3a7ef26 100644 --- a/src/parser/parser.hpp +++ b/src/parser/parser.hpp @@ -6,6 +6,7 @@ #include "../lexer/lexer.hpp" #include "../typechecker/type.hpp" +#include "../ir/ir.hpp" namespace Solstice { @@ -43,6 +44,7 @@ namespace Solstice { std::variant data; PossibleType ptype; + Argument arg; Node() = delete; Node(NodeType type) : type(type) {} diff --git a/src/typechecker/typechecker.cpp b/src/typechecker/typechecker.cpp index 2c36fbc..5614722 100644 --- a/src/typechecker/typechecker.cpp +++ b/src/typechecker/typechecker.cpp @@ -6,7 +6,7 @@ namespace Solstice { void TypeChecker::initOverloads() { - types = { + context.types = { {"Int", {BaseType::Int}}, {"String", {BaseType::String}}, {"Double", {BaseType::Double}}, @@ -19,7 +19,7 @@ namespace Solstice { {"Object", {BaseType::Tuple}}, }; - addOverloads = { + context.addOverloads = { {{BaseType::Int, BaseType::Int}, BaseType::Int}, {{BaseType::Double, BaseType::Double}, BaseType::Double}, {{BaseType::Int, BaseType::Double}, BaseType::Double}, @@ -29,28 +29,28 @@ namespace Solstice { {{BaseType::Char, BaseType::String}, BaseType::String}, {{BaseType::Char, BaseType::Char}, BaseType::Char} }; - subtractOverloads = { + context.subtractOverloads = { {{BaseType::Int, BaseType::Int}, BaseType::Int}, {{BaseType::Double, BaseType::Double}, BaseType::Double}, {{BaseType::Int, BaseType::Double}, BaseType::Double}, {{BaseType::Double, BaseType::Int}, BaseType::Double}, {{BaseType::Char, BaseType::Char}, BaseType::Char} }; - multiplyOverloads = { + context.multiplyOverloads = { {{BaseType::Int, BaseType::Int}, BaseType::Int}, {{BaseType::Double, BaseType::Double}, BaseType::Double}, {{BaseType::Int, BaseType::Double}, BaseType::Double}, {{BaseType::Double, BaseType::Int}, BaseType::Double}, {{BaseType::Char, BaseType::Char}, BaseType::Char} }; - divideOverloads = { + context.divideOverloads = { {{BaseType::Int, BaseType::Int}, BaseType::Double}, {{BaseType::Double, BaseType::Double}, BaseType::Double}, {{BaseType::Int, BaseType::Double}, BaseType::Double}, {{BaseType::Double, BaseType::Int}, BaseType::Double}, {{BaseType::Char, BaseType::Char}, BaseType::Char} }; - equalOverloads = { + context.equalOverloads = { {{BaseType::Int, BaseType::Int}, BaseType::Bool}, {{BaseType::Double, BaseType::Double}, BaseType::Bool}, {{BaseType::Int, BaseType::Double}, BaseType::Bool}, @@ -59,7 +59,7 @@ namespace Solstice { {{BaseType::Char, BaseType::Char}, BaseType::Bool}, {{BaseType::Bool, BaseType::Bool}, BaseType::Bool}, }; - notEqualOverloads = { + context.notEqualOverloads = { {{BaseType::Int, BaseType::Int}, BaseType::Bool}, {{BaseType::Double, BaseType::Double}, BaseType::Bool}, {{BaseType::Int, BaseType::Double}, BaseType::Bool}, @@ -68,14 +68,14 @@ namespace Solstice { {{BaseType::Char, BaseType::Char}, BaseType::Bool}, {{BaseType::Bool, BaseType::Bool}, BaseType::Bool}, }; - greaterThanOverloads = { + context.greaterThanOverloads = { {{BaseType::Int, BaseType::Int}, BaseType::Bool}, {{BaseType::Double, BaseType::Double}, BaseType::Bool}, {{BaseType::Int, BaseType::Double}, BaseType::Bool}, {{BaseType::Double, BaseType::Int}, BaseType::Bool}, {{BaseType::Char, BaseType::Char}, BaseType::Bool}, }; - lesserThanOverloads = { + context.lesserThanOverloads = { {{BaseType::Int, BaseType::Int}, BaseType::Bool}, {{BaseType::Double, BaseType::Double}, BaseType::Bool}, {{BaseType::Int, BaseType::Double}, BaseType::Bool}, @@ -117,11 +117,11 @@ namespace Solstice { throw std::runtime_error("FIXME identifier node does not contain identifier"); } - if (variables.find(*identifier) == variables.end()) { + if (context.variables.find(*identifier) == context.variables.end()) { throw std::runtime_error("unknown variable " + *identifier); } - node.ptype = variables[*identifier]; + node.ptype = context.variables[*identifier]; } void TypeChecker::checkTupleNodeType(Node& node) { @@ -136,7 +136,7 @@ namespace Solstice { if (!id.has_value()) { throw std::runtime_error("FIXME identifier node does not contain identifier"); } - if (variables.find(*id) != variables.end()) { + if (context.variables.find(*id) != context.variables.end()) { throw std::runtime_error("Cannot overwrite existing bind/variable/function with new bind"); } @@ -144,12 +144,12 @@ namespace Solstice { if (node.children[1].ptype.possiblities.empty() && !node.children[1].ptype.unknownType) { throw std::runtime_error("cannot assign name with no possible types"); } - variables[*id] = node.children[1].ptype; - variables[*id].isConstant = true; + context.variables[*id] = node.children[1].ptype; + context.variables[*id].isConstant = true; // if not narrowed down to a single type yet, keep it open so later // usages of this name can continue narrowing it - if (!variables[*id].getOnlyType().has_value()) { - variables[*id].unknownType = true; + if (!context.variables[*id].getOnlyType().has_value()) { + context.variables[*id].unknownType = true; } } static inline std::vector> doCartesianProductOnTypeSets(const std::vector& typeSets) { @@ -197,7 +197,7 @@ namespace Solstice { if (!id.has_value()) { throw std::runtime_error("FIXME identifier node does not contain identifier"); } - if (variables.find(*id) != variables.end()) { + if (context.variables.find(*id) != context.variables.end()) { throw std::runtime_error("Cannot overwrite existing bind/variable/function with new function"); } @@ -227,11 +227,11 @@ namespace Solstice { if (!typeId.has_value()) { throw std::runtime_error("FIXME identifier node does not contain identifier"); } - if (types.find(*typeId) == types.end()) { + if (context.types.find(*typeId) == context.types.end()) { throw std::runtime_error("unknown type " + *typeId); } auto name = *arg.children[0].getIdentifier(); - typeChecker.setVariable(name, types[*typeId]); + typeChecker.setVariable(name, context.types[*typeId]); paramNames.push_back(name); break; } @@ -246,7 +246,7 @@ namespace Solstice { // collect the (possibly still-polymorphic) narrowed argument types Function fn; for (const auto& name : paramNames) { - fn.argumentTypes.push_back(typeChecker.variables[name]); + fn.argumentTypes.push_back(typeChecker.context.variables[name]); } std::vector> candidates = doCartesianProductOnTypeSets(fn.argumentTypes); @@ -276,23 +276,23 @@ namespace Solstice { } } - functions[*id] = fn; + context.functions[*id] = fn; } Narrowing TypeChecker::doesNodeChildrenNeedNarrowing(Node& node) { Narrowing ret = Narrowing::None; if ( node.children[0].type == NodeType::Identifier && - variables.find(*node.children[0].getIdentifier()) != variables.end() && - variables[*node.children[0].getIdentifier()].unknownType + context.variables.find(*node.children[0].getIdentifier()) != context.variables.end() && + context.variables[*node.children[0].getIdentifier()].unknownType ) { ret = Narrowing::Left; } if ( node.children[1].type == NodeType::Identifier && - variables.find(*node.children[1].getIdentifier()) != variables.end() && - variables[*node.children[1].getIdentifier()].unknownType + context.variables.find(*node.children[1].getIdentifier()) != context.variables.end() && + context.variables[*node.children[1].getIdentifier()].unknownType ) { if (ret == Narrowing::Left) { ret = Narrowing::Both; @@ -318,8 +318,8 @@ namespace Solstice { // narrow types if we need to switch (doesNodeChildrenNeedNarrowing(node)) { case Narrowing::Both: { - auto& leftVar = variables[*left.getIdentifier()]; - auto& rightVar = variables[*right.getIdentifier()]; + auto& leftVar = context.variables[*left.getIdentifier()]; + auto& rightVar = context.variables[*right.getIdentifier()]; // an empty possibility set means "still fully open" (no prior // narrowing yet), so treat it as no constraint; otherwise only @@ -344,7 +344,7 @@ namespace Solstice { break; } case Narrowing::Left: { - auto& leftVar = variables[*left.getIdentifier()]; + auto& leftVar = context.variables[*left.getIdentifier()]; std::unordered_set newLeft; for (const auto& [key, value] : overloads) { bool leftOk = leftVar.possiblities.empty() || leftVar.possiblities.find(key.left) != leftVar.possiblities.end(); @@ -361,7 +361,7 @@ namespace Solstice { break; } case Narrowing::Right: { - auto& rightVar = variables[*right.getIdentifier()]; + auto& rightVar = context.variables[*right.getIdentifier()]; std::unordered_set newRight; for (const auto& [key, value] : overloads) { bool leftOk = left.ptype.possiblities.find(key.left) != left.ptype.possiblities.end(); @@ -400,11 +400,11 @@ namespace Solstice { throw std::runtime_error("identifier node does not contain identifier"); } - if (functions.find(*name) == functions.end()) { + if (context.functions.find(*name) == context.functions.end()) { throw std::runtime_error("unknown function " + *name); } - auto& function = functions[*name]; + auto& function = context.functions[*name]; node.ptype.possiblities.clear(); node.ptype.unknownType = false; @@ -435,7 +435,7 @@ namespace Solstice { } child.ptype.possiblities = newTypes; } - variables[*id].possiblities = child.ptype.possiblities; + context.variables[*id].possiblities = child.ptype.possiblities; } args.push_back(child.ptype); } @@ -466,8 +466,8 @@ namespace Solstice { if (!id.has_value()) { throw std::runtime_error("FIXME identifier node does not contain identifier"); } - if (variables.find(*id) != variables.end()) { - if (variables[*id].isConstant) { + if (context.variables.find(*id) != context.variables.end()) { + if (context.variables[*id].isConstant) { throw std::runtime_error("cannot reassign existing bind"); } } @@ -479,11 +479,11 @@ namespace Solstice { if (node.children[1].ptype.possiblities.empty() && !node.children[1].ptype.unknownType) { throw std::runtime_error("cannot assign name with no possible types"); } - variables[*id] = node.children[1].ptype; + context.variables[*id] = node.children[1].ptype; // if not narrowed down to a single type yet, keep it open so later // usages of this name can continue narrowing it - if (!variables[*id].getOnlyType().has_value()) { - variables[*id].unknownType = true; + if (!context.variables[*id].getOnlyType().has_value()) { + context.variables[*id].unknownType = true; } } @@ -505,7 +505,7 @@ namespace Solstice { checkNodeType(node.children[1]); // narrow if required - narrowBinaryNode(node, addOverloads); + narrowBinaryNode(node, context.addOverloads); } @@ -513,49 +513,49 @@ namespace Solstice { checkNodeType(node.children[0]); checkNodeType(node.children[1]); - narrowBinaryNode(node, subtractOverloads); + narrowBinaryNode(node, context.subtractOverloads); } void TypeChecker::checkMultiplyType(Node& node) { checkNodeType(node.children[0]); checkNodeType(node.children[1]); - narrowBinaryNode(node, multiplyOverloads); + narrowBinaryNode(node, context.multiplyOverloads); } void TypeChecker::checkDivideType(Node& node) { checkNodeType(node.children[0]); checkNodeType(node.children[1]); - narrowBinaryNode(node, divideOverloads); + narrowBinaryNode(node, context.divideOverloads); } void TypeChecker::checkEqualType(Node& node) { checkNodeType(node.children[0]); checkNodeType(node.children[1]); - narrowBinaryNode(node, equalOverloads); + narrowBinaryNode(node, context.equalOverloads); } void TypeChecker::checkNotEqualType(Node& node) { checkNodeType(node.children[0]); checkNodeType(node.children[1]); - narrowBinaryNode(node, notEqualOverloads); + narrowBinaryNode(node, context.notEqualOverloads); } void TypeChecker::checkGreaterThanType(Node& node) { checkNodeType(node.children[0]); checkNodeType(node.children[1]); - narrowBinaryNode(node, greaterThanOverloads); + narrowBinaryNode(node, context.greaterThanOverloads); } void TypeChecker::checkLesserThanType(Node& node) { checkNodeType(node.children[0]); checkNodeType(node.children[1]); - narrowBinaryNode(node, lesserThanOverloads); + narrowBinaryNode(node, context.lesserThanOverloads); } void TypeChecker::checkCImportType(Node& node) { @@ -564,25 +564,25 @@ namespace Solstice { // Get function's type Function function; auto returnTypeId = *node.children[1].getIdentifier(); - if (types.find(returnTypeId) == types.end()) { + if (context.types.find(returnTypeId) == context.types.end()) { throw std::runtime_error("Unknown type " + returnTypeId); } - auto returnType = types[returnTypeId]; + auto returnType = context.types[returnTypeId]; std::vector functionTypes; for (const auto& arg : node.children[2].children) { auto argTypeId = *arg.getIdentifier(); - if (types.find(argTypeId) == types.end()) { + if (context.types.find(argTypeId) == context.types.end()) { throw std::runtime_error("Unknown type " + argTypeId); } - functionTypes.push_back(types[argTypeId]); + functionTypes.push_back(context.types[argTypeId]); } function.returnTypes[functionTypes] = returnType; - if (functions.find(name) != functions.end()) { + if (context.functions.find(name) != context.functions.end()) { throw std::runtime_error("cannot override function with name " + name); } - functions[name] = function; + context.functions[name] = function; } void TypeChecker::checkNodeType(Node& node) { diff --git a/src/typechecker/typechecker.hpp b/src/typechecker/typechecker.hpp index 8263903..f1c5683 100644 --- a/src/typechecker/typechecker.hpp +++ b/src/typechecker/typechecker.hpp @@ -15,7 +15,7 @@ namespace Solstice { Both }; - class TypeChecker { + struct Context { std::unordered_map variables; std::unordered_map types; std::unordered_map functions; @@ -28,6 +28,10 @@ namespace Solstice { std::unordered_map notEqualOverloads; std::unordered_map greaterThanOverloads; std::unordered_map lesserThanOverloads; + }; + + class TypeChecker { + Context context; Node* input; @@ -78,21 +82,25 @@ namespace Solstice { // Only for use as a public function. void setVariable(const std::string& name, const Type& type) { - variables[name] = {{type}}; + context.variables[name] = {{type}}; } void setVariableUnknown(const std::string& name) { - variables[name].unknownType = true; + context.variables[name].unknownType = true; } void checkTypes(); const Function* getFunction(const std::string& name) const { - auto it = functions.find(name); - if (it == functions.end()) { + auto it = context.functions.find(name); + if (it == context.functions.end()) { return nullptr; } return &it->second; } + + Context& getContext() { + return context; + } }; }