First common arch-independent IR prototype (no structs or arrays yet)

This commit is contained in:
Nikita Lisitsa 2026-03-18 18:35:47 +03:00
parent 1a0dd2a48f
commit 25672d3f05
12 changed files with 982 additions and 3 deletions

View file

@ -6,6 +6,7 @@ set(CMAKE_CXX_STANDARD 23)
add_subdirectory(libs/types) add_subdirectory(libs/types)
add_subdirectory(libs/ast) add_subdirectory(libs/ast)
add_subdirectory(libs/parser) add_subdirectory(libs/parser)
add_subdirectory(libs/ir)
add_subdirectory(libs/jit) add_subdirectory(libs/jit)
add_subdirectory(libs/interpreter) add_subdirectory(libs/interpreter)

View file

@ -5,6 +5,8 @@
#include <pslang/ast/statement.hpp> #include <pslang/ast/statement.hpp>
#include <pslang/ast/preprocess.hpp> #include <pslang/ast/preprocess.hpp>
#include <pslang/ast/print.hpp> #include <pslang/ast/print.hpp>
#include <pslang/ir/compiler.hpp>
#include <pslang/ir/print.hpp>
#include <pslang/jit/jit.hpp> #include <pslang/jit/jit.hpp>
#include <pslang/jit/executable.hpp> #include <pslang/jit/executable.hpp>
#include <pslang/jit/foreign.hpp> #include <pslang/jit/foreign.hpp>
@ -77,6 +79,7 @@ int main(int argc, char ** argv)
std::cout << " -t, --trace Trace each line of execution\n"; std::cout << " -t, --trace Trace each line of execution\n";
std::cout << " -d, --dump Dump all variables after processing all files\n"; std::cout << " -d, --dump Dump all variables after processing all files\n";
std::cout << " -p, --print Print the AST after parsing each file\n"; std::cout << " -p, --print Print the AST after parsing each file\n";
std::cout << " -i, --ir Print the IR after parsing each file\n";
std::cout << " -j, --jit Just-in-time compile & execute the code instead of interpreting\n"; std::cout << " -j, --jit Just-in-time compile & execute the code instead of interpreting\n";
return 0; return 0;
} }
@ -88,6 +91,7 @@ int main(int argc, char ** argv)
bool dump = false; bool dump = false;
bool dump_ast = false; bool dump_ast = false;
bool dump_ir = false;
bool jit = false; bool jit = false;
std::vector<std::string> filenames; std::vector<std::string> filenames;
@ -113,6 +117,12 @@ int main(int argc, char ** argv)
continue; continue;
} }
if (std::strcmp(argv[arg], "-i") == 0 || std::strcmp(argv[arg], "--ir") == 0)
{
dump_ir = true;
continue;
}
if (std::strcmp(argv[arg], "-j") == 0 || std::strcmp(argv[arg], "--jit") == 0) if (std::strcmp(argv[arg], "-j") == 0 || std::strcmp(argv[arg], "--jit") == 0)
{ {
jit = true; jit = true;
@ -177,6 +187,19 @@ int main(int argc, char ** argv)
std::cout << std::flush; std::cout << std::flush;
} }
// NB: IR isn't used in JIT or interpreter right now.
if (dump_ir)
{
for (std::size_t i = 0; i < filenames.size(); ++i)
{
std::cout << "Input file " << filenames[i] << " IR dump:\n\n";
ir::module_context context;
ir::compile(context, parsed[i]);
ir::print(std::cout, context);
std::cout << "\n";
}
}
if (jit) if (jit)
{ {
jit::program_context pcontext jit::program_context pcontext

7
examples/ir_test.psl Normal file
View file

@ -0,0 +1,7 @@
func test(x: f32) -> f32:
foreign func sin(x: f32) -> f32
let pi = 3.1415926535
return sin(pi * x)
let myfunc = test
let x = myfunc(7.5)

View file

@ -4,6 +4,8 @@ func g() -> u16:
func h() -> bool: func h() -> bool:
return h() return h()
func test() -> u16: func test_and() -> u16:
return 65535us || g() return 0us && g()
func test_or() -> u16:
return 65535us || g()

6
libs/ir/CMakeLists.txt Normal file
View file

@ -0,0 +1,6 @@
file(GLOB_RECURSE PSLANG_IR_HEADERS "${CMAKE_CURRENT_SOURCE_DIR}/include/*.hpp")
file(GLOB_RECURSE PSLANG_IR_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/source/*.cpp")
add_library(pslang-ir STATIC ${PSLANG_IR_HEADERS} ${PSLANG_IR_SOURCES})
target_include_directories(pslang-ir PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include")
target_link_libraries(pslang-ir PUBLIC pslang-types pslang-ast)

View file

@ -0,0 +1,30 @@
#pragma once
#include <pslang/ast/statement_fwd.hpp>
#include <pslang/ir/node.hpp>
#include <unordered_map>
namespace pslang::ast
{
struct function_definition;
}
namespace pslang::ir
{
struct module_context
{
node_list nodes;
std::unordered_map<node const *, std::string> labels;
std::unordered_map<ast::function_definition const *, node_ref> symbols;
node_ref entry_point;
};
void compile(module_context & context, ast::statement_list_ptr const & statements);
}

View file

@ -0,0 +1,119 @@
#pragma once
#include <pslang/ir/node_fwd.hpp>
#include <pslang/ast/literal.hpp>
#include <pslang/ast/operation.hpp>
#include <pslang/types/type.hpp>
#include <variant>
namespace pslang::ir
{
// Used primarily as jump target
struct nop
{};
struct literal
{
// NB: no unit values - there's no point in storing them anywhere
ast::literal value;
};
struct unary_operation
{
ast::unary_operation_type type;
node_ref arg1;
};
struct binary_operation
{
ast::binary_operation_type type;
node_ref arg1;
node_ref arg2;
};
struct cast_operation
{
node_ref arg1;
types::type_ptr target_type;
};
struct argument
{
std::size_t index;
};
struct address
{
node_ref target;
};
struct extern_symbol
{
std::string name;
};
struct assignment
{
node_ref lhs;
node_ref rhs;
};
struct jump
{
node_ref target;
};
struct jump_if_zero
{
// Condition must be a bool-valued node
node_ref condition;
node_ref target;
};
// Call to a specific node
struct call
{
node_ref target;
std::vector<node_ref> arguments;
};
// Call to a pointer stored in a node
struct call_pointer
{
node_ref pointer;
std::vector<node_ref> arguments;
};
struct return_value
{
std::optional<node_ref> value;
};
using instruction = std::variant<
nop,
literal,
unary_operation,
binary_operation,
cast_operation,
argument,
address,
extern_symbol,
assignment,
jump,
jump_if_zero,
call,
call_pointer,
return_value
>;
struct node
{
instruction instruction;
// Null for non-value instructions
types::type_ptr inferred_type = nullptr;
};
}

View file

@ -0,0 +1,12 @@
#pragma once
#include <list>
namespace pslang::ir
{
struct node;
using node_list = std::list<node>;
using node_ref = node_list::iterator;
}

View file

@ -0,0 +1,15 @@
#pragma once
#include <pslang/ir/node_fwd.hpp>
#include <iostream>
namespace pslang::ir
{
struct module_context;
void print(std::ostream & out, node_list const & nodes);
void print(std::ostream & out, module_context const & context);
}

462
libs/ir/source/compiler.cpp Normal file
View file

@ -0,0 +1,462 @@
#include <pslang/ir/compiler.hpp>
#include <pslang/ir/node.hpp>
#include <pslang/ast/statement_visitor.hpp>
#include <pslang/ast/expression_visitor.hpp>
#include <unordered_map>
#include <unordered_set>
namespace pslang::ir
{
namespace
{
struct local_context
{
std::unordered_map<ast::function_definition const *, node_ref> functions;
struct scope
{
std::string label_prefix;
std::unordered_map<std::string, node_ref> variables;
std::unordered_set<std::string> foreign_functions;
std::unordered_map<std::string, ast::function_definition const *> functions;
std::unordered_map<std::string, ast::struct_definition const *> structs;
};
std::vector<scope> scopes;
struct resolve_address_data
{
node_ref address;
ast::function_definition const * target;
};
struct resolve_call_data
{
node_ref call;
ast::function_definition const * target;
};
std::vector<resolve_address_data> resolve_address;
std::vector<resolve_call_data> resolve_call;
};
// Iterate over a single scope (i.e. not visiting subscopes recursively)
// and add all defined functions & foreign functions to the current scope
struct populate_symbols_visitor
: ast::const_statement_visitor<populate_symbols_visitor>
{
local_context & lcontext;
using const_statement_visitor::apply;
void apply(ast::expression_ptr const &) {}
void apply(ast::assignment const &) {}
void apply(ast::variable_declaration const &) {}
void apply(ast::if_block const &) {}
void apply(ast::else_block const &) {}
void apply(ast::else_if_block const &) {}
void apply(ast::if_chain const &) {}
void apply(ast::while_block const &) {}
void apply(ast::function_definition const & node)
{
lcontext.scopes.back().functions[node.name] = &node;
}
void apply(ast::foreign_function_declaration const & node)
{
lcontext.scopes.back().foreign_functions.insert(node.name);
}
void apply(ast::return_statement const &) {}
void apply(ast::field_definition const &) {}
void apply(ast::struct_definition const & node)
{
lcontext.scopes.back().structs[node.name] = &node;
}
};
// Compile a single function and store the entry point node_ref
// in local_context
struct compile_function_visitor
: ast::const_statement_visitor<compile_function_visitor>
, ast::const_expression_visitor<compile_function_visitor>
{
using const_statement_visitor::apply;
using const_expression_visitor::apply;
module_context & mcontext;
local_context & lcontext;
template <typename Node>
node_ref apply(Node const & node)
{
throw std::runtime_error(std::string("IR compile visitor not implemented for ") + typeid(Node).name());
}
// Expressions
template <typename T>
node_ref apply(ast::primitive_literal_base<T> const & node)
{
mcontext.nodes.emplace_back(literal{node}, ast::get_type(node));
return last();
}
node_ref apply(ast::identifier const & node)
{
for (auto it = lcontext.scopes.rbegin(); it != lcontext.scopes.rend(); ++it)
{
if (auto jt = it->variables.find(node.name); jt != it->variables.end())
return jt->second;
if (auto jt = it->foreign_functions.find(node.name); jt != it->foreign_functions.end())
{
mcontext.nodes.emplace_back(extern_symbol{node.name}, node.inferred_type);
return last();
}
if (auto jt = it->functions.find(node.name); jt != it->functions.end())
{
mcontext.nodes.emplace_back(address{}, node.inferred_type);
lcontext.resolve_address.emplace_back(last(), jt->second);
return last();
}
}
throw std::runtime_error("Unknown identifier \"" + node.name + "\"");
}
node_ref apply(ast::unary_operation const & node)
{
auto arg1 = apply(*node.arg1);
mcontext.nodes.emplace_back(unary_operation{node.type, arg1}, node.inferred_type);
return last();
}
node_ref apply(ast::binary_operation const & node)
{
auto arg1 = apply(*node.arg1);
if (node.type == ast::binary_operation_type::logical_and)
{
mcontext.nodes.emplace_back(jump_if_zero{arg1});
auto jump = last();
auto arg2 = apply(*node.arg2);
mcontext.nodes.emplace_back(binary_operation{ast::binary_operation_type::binary_and, arg1, arg2}, node.inferred_type);
mcontext.nodes.emplace_back(assignment{arg1, last()});
mcontext.nodes.emplace_back(nop{});
std::get<jump_if_zero>(jump->instruction).target = last();
return arg1;
}
if (node.type == ast::binary_operation_type::logical_or)
{
mcontext.nodes.emplace_back(unary_operation{ast::unary_operation_type::logical_not, arg1}, node.inferred_type);
mcontext.nodes.emplace_back(jump_if_zero{last()});
auto jump = last();
auto arg2 = apply(*node.arg2);
mcontext.nodes.emplace_back(binary_operation{ast::binary_operation_type::binary_or, arg1, arg2}, node.inferred_type);
mcontext.nodes.emplace_back(assignment{arg1, last()});
mcontext.nodes.emplace_back(nop{});
std::get<jump_if_zero>(jump->instruction).target = last();
return arg1;
}
auto arg2 = apply(*node.arg2);
mcontext.nodes.emplace_back(binary_operation{node.type, arg1, arg2}, node.inferred_type);
return last();
}
node_ref apply(ast::cast_operation const & node)
{
auto arg = apply(*node.expression);
mcontext.nodes.emplace_back(cast_operation{arg, node.inferred_type}, node.inferred_type);
return last();
}
node_ref apply(ast::function_call const & node)
{
if (node.function)
{
std::vector<node_ref> arguments;
for (auto const & argument : node.arguments)
arguments.push_back(apply(*argument));
if (auto identifier = std::get_if<ast::identifier>(node.function.get()))
{
for (auto it = lcontext.scopes.rbegin(); it != lcontext.scopes.rend(); ++it)
{
if (auto jt = it->functions.find(identifier->name); jt != it->functions.end())
{
mcontext.nodes.emplace_back(call{{}, std::move(arguments)}, node.inferred_type);
lcontext.resolve_call.emplace_back(last(), jt->second);
return last();
}
}
}
auto function = apply(*node.function);
mcontext.nodes.emplace_back(call_pointer{function, std::move(arguments)}, node.inferred_type);
return last();
}
else // if (node.type)
{
throw std::runtime_error("IR compile visitor not implemented for type constructors");
}
}
// TODO: array, array_access, field_access
// Statements
node_ref apply(ast::expression_ptr const & node)
{
apply(*node);
return last();
}
node_ref apply(ast::assignment const & node)
{
auto rhs = apply(*node.rhs);
auto lhs = apply(*node.lhs);
mcontext.nodes.emplace_back(assignment{lhs, rhs}, ast::get_type(*node.rhs));
return last();
}
node_ref apply(ast::variable_declaration const & node)
{
apply(*node.initializer);
lcontext.scopes.back().variables[node.name] = last();
return last();
}
node_ref apply(ast::if_chain const & node)
{
std::vector<node_ref> jumps_to_end;
for (auto const & block : node.blocks)
{
std::optional<node_ref> jump_to_next;
if (block.condition)
{
auto condition = apply(*block.condition);
mcontext.nodes.emplace_back(jump_if_zero{condition, {}});
jump_to_next = last();
}
lcontext.scopes.emplace_back();
apply(*block.statements);
lcontext.scopes.pop_back();
mcontext.nodes.emplace_back(jump{});
jumps_to_end.push_back(last());
mcontext.nodes.emplace_back(nop{});
if (jump_to_next)
std::get<jump_if_zero>((*jump_to_next)->instruction).target = last();
}
auto end = last();
for (auto const & jump_to_end : jumps_to_end)
std::get<jump>(jump_to_end->instruction).target = end;
return end;
}
node_ref apply(ast::while_block const & node)
{
auto before = last();
auto condition = apply(*node.condition);
mcontext.nodes.emplace_back(jump_if_zero{condition, {}});
auto jump1 = last();
lcontext.scopes.emplace_back();
apply(*node.statements);
lcontext.scopes.pop_back();
mcontext.nodes.emplace_back(jump{std::next(before)});
mcontext.nodes.emplace_back(nop{});
std::get<jump_if_zero>(jump1->instruction).target = last();
return last();
}
node_ref apply(ast::function_definition const & node)
{
return last();
}
node_ref apply(ast::foreign_function_declaration const & node)
{
return last();
}
node_ref apply(ast::return_statement const & node)
{
if (node.value)
{
auto value = apply(*node.value);
mcontext.nodes.emplace_back(return_value{value}, value->inferred_type);
}
else
mcontext.nodes.emplace_back(return_value{}, std::make_shared<types::type>(types::unit_type{}));
return last();
}
// TODO: struct_definition, field_definition
// Statement list
void apply(ast::statement_list const & list)
{
populate_symbols_visitor{{}, lcontext}.apply(list);
for (auto const & statement : list.statements)
apply(*statement);
}
void do_apply(ast::function_definition const & node)
{
auto before = last();
lcontext.scopes.emplace_back();
for (std::size_t i = 0; i < node.arguments.size(); ++i)
{
mcontext.nodes.emplace_back(argument{i}, ast::get_type(*node.arguments[i].type));
lcontext.scopes.back().variables[node.arguments[i].name] = last();
}
apply(*node.statements);
if (types::equal(*ast::get_type(*node.return_type), types::unit_type{}))
mcontext.nodes.emplace_back(return_value{}, std::make_shared<types::type>(types::unit_type{}));
lcontext.scopes.pop_back();
auto entry = std::next(before);
lcontext.functions[&node] = entry;
mcontext.labels[&(*entry)] = lcontext.scopes.back().label_prefix + node.name;
}
private:
node_ref last()
{
return std::prev(mcontext.nodes.end());
}
};
// Main compilation visitor
struct compile_visitor
: ast::const_statement_visitor<compile_visitor>
{
module_context & mcontext;
local_context & lcontext;
using const_statement_visitor::apply;
void apply(ast::expression_ptr const &) {}
void apply(ast::assignment const &) {}
void apply(ast::variable_declaration const &) {}
void apply(ast::if_block const &) {}
void apply(ast::else_block const &) {}
void apply(ast::else_if_block const &) {}
void apply(ast::if_chain const & node)
{
for (auto const & block : node.blocks)
{
lcontext.scopes.emplace_back();
apply(*block.statements);
lcontext.scopes.pop_back();
}
}
void apply(ast::while_block const & node)
{
lcontext.scopes.emplace_back();
apply(*node.statements);
lcontext.scopes.pop_back();
}
void apply(ast::function_definition const & node)
{
compile_function_visitor{{}, {}, mcontext, lcontext}.do_apply(node);
std::string label_prefix;
if (!lcontext.scopes.empty())
label_prefix = lcontext.scopes.back().label_prefix + node.name + ".";
lcontext.scopes.emplace_back(std::move(label_prefix));
apply(*node.statements);
// Don't pop_back entry point scope
if (lcontext.scopes.size() > 1)
lcontext.scopes.pop_back();
}
void apply(ast::foreign_function_declaration const &) {}
void apply(ast::return_statement const &) {}
void apply(ast::field_definition const &) {}
void apply(ast::struct_definition const &) {}
void apply(ast::statement_list const & list)
{
populate_symbols_visitor{{}, lcontext}.apply(list);
for (auto const & statement : list.statements)
apply(*statement);
}
};
}
void compile(module_context & mcontext, ast::statement_list_ptr const & statements)
{
// Add a fake AST node for the entry point
auto root = std::make_shared<ast::statement>(ast::function_definition{
{
{},
{},
std::make_shared<ast::type>(types::unit_type{}),
{},
},
statements,
{},
});
mcontext.nodes.emplace_back(nop{});
auto extra_nop = std::prev(mcontext.nodes.end());
local_context lcontext;
compile_visitor{{}, mcontext, lcontext}.apply(*root);
mcontext.labels[&(*std::next(extra_nop))] = "[entry point]";
mcontext.nodes.erase(extra_nop);
for (auto const & resolve : lcontext.resolve_address)
std::get<address>(resolve.address->instruction).target = lcontext.functions.at(resolve.target);
for (auto const & resolve : lcontext.resolve_call)
std::get<call>(resolve.call->instruction).target = lcontext.functions.at(resolve.target);
for (auto const & symbol : lcontext.scopes.front().functions)
mcontext.symbols[symbol.second] = lcontext.functions.at(symbol.second);
mcontext.entry_point = lcontext.functions.at(std::get_if<ast::function_definition>(root.get()));
}
}

302
libs/ir/source/print.cpp Normal file
View file

@ -0,0 +1,302 @@
#include <pslang/ir/print.hpp>
#include <pslang/ir/node.hpp>
#include <pslang/ir/compiler.hpp>
#include <pslang/types/print.hpp>
#include <unordered_map>
#include <iomanip>
namespace pslang::ir
{
namespace
{
void print(std::ostream & out, ast::unary_operation_type type)
{
switch (type)
{
case ast::unary_operation_type::negation:
out << "neg";
break;
case ast::unary_operation_type::logical_not:
out << "not";
break;
}
}
void print(std::ostream & out, ast::binary_operation_type type)
{
switch (type)
{
case ast::binary_operation_type::addition:
out << "add";
break;
case ast::binary_operation_type::subtraction:
out << "sub";
break;
case ast::binary_operation_type::multiplication:
out << "mul";
break;
case ast::binary_operation_type::division:
out << "div";
break;
case ast::binary_operation_type::remainder:
out << "rem";
break;
case ast::binary_operation_type::binary_and:
out << "and";
break;
case ast::binary_operation_type::logical_and:
out << "and sc";
break;
case ast::binary_operation_type::binary_or:
out << "or";
break;
case ast::binary_operation_type::logical_or:
out << "or sc";
break;
case ast::binary_operation_type::logical_xor:
out << "xor";
break;
case ast::binary_operation_type::equals:
out << "eq";
break;
case ast::binary_operation_type::not_equals:
out << "neq";
break;
case ast::binary_operation_type::less:
out << "lt";
break;
case ast::binary_operation_type::greater:
out << "gt";
break;
case ast::binary_operation_type::less_equals:
out << "leq";
break;
case ast::binary_operation_type::greater_equals:
out << "geq";
break;
}
}
struct print_literal_visitor
{
std::ostream & out;
void operator()(ast::bool_literal const & literal)
{
out << (literal.value ? "true" : "false");
}
void operator()(ast::i8_literal const & literal)
{
out << int(literal.value);
}
void operator()(ast::i16_literal const & literal)
{
out << literal.value;
}
void operator()(ast::i32_literal const & literal)
{
out << literal.value;
}
void operator()(ast::i64_literal const & literal)
{
out << literal.value;
}
void operator()(ast::u8_literal const & literal)
{
out << int(literal.value);
}
void operator()(ast::u16_literal const & literal)
{
out << literal.value;
}
void operator()(ast::u32_literal const & literal)
{
out << literal.value;
}
void operator()(ast::u64_literal const & literal)
{
out << literal.value;
}
void operator()(ast::f16_literal const & literal)
{
out << literal.value.repr;
}
void operator()(ast::f32_literal const & literal)
{
out << literal.value;
}
void operator()(ast::f64_literal const & literal)
{
out << literal.value;
}
};
struct print_visitor
{
std::ostream & out;
std::unordered_map<node const *, std::size_t> node_index;
std::size_t current_index = 0;
std::size_t indent = 0;
void fill_index(node_list const & nodes)
{
std::size_t index = 0;
for (auto const & node : nodes)
node_index[&node] = index++;
if (!node_index.empty())
{
std::size_t max_index = node_index.size() - 1;
while (max_index > 0)
{
++indent;
max_index /= 10;
}
}
}
std::size_t get_index(node_ref ref)
{
return node_index.at(&(*ref));
}
void prelude()
{
out << std::right << std::setfill(' ') << std::setw(indent);
}
void operator()(nop const &)
{
out << "nop";
}
void operator()(literal const & instruction)
{
out << "lit ";
std::visit(print_literal_visitor{out}, instruction.value);
}
void operator()(unary_operation const & instruction)
{
print(out, instruction.type);
out << " $" << get_index(instruction.arg1);
}
void operator()(binary_operation const & instruction)
{
print(out, instruction.type);
out << " $" << get_index(instruction.arg1) << " $" << get_index(instruction.arg2);
}
void operator()(cast_operation const & instruction)
{
out << "cast $" << get_index(instruction.arg1);
}
void operator()(argument const & instruction)
{
out << "arg #" << instruction.index;
}
void operator()(address const & instruction)
{
out << "addr $" << get_index(instruction.target);
}
void operator()(extern_symbol const & instruction)
{
out << "extern \"" << instruction.name << "\"";
}
void operator()(assignment const & instruction)
{
out << "assign $" << get_index(instruction.lhs) << " $" << get_index(instruction.rhs);
}
void operator()(jump const & instruction)
{
out << "jump $" << get_index(instruction.target);
}
void operator()(jump_if_zero const & instruction)
{
out << "jz $" << get_index(instruction.condition) << " $" << get_index(instruction.target);
}
void operator()(call const & instruction)
{
out << "call $" << get_index(instruction.target);
for (auto const & arument : instruction.arguments)
out << " $" << get_index(arument);
}
void operator()(call_pointer const & instruction)
{
out << "callp $" << get_index(instruction.pointer);
for (auto const & arument : instruction.arguments)
out << " $" << get_index(arument);
}
void operator()(return_value const & instruction)
{
out << "ret ";
if (instruction.value)
out << "$" << get_index(*instruction.value);
}
};
void print_impl(std::ostream & out, node_list const & nodes, module_context const * context)
{
print_visitor visitor{out};
visitor.fill_index(nodes);
std::size_t index = 0;
for (auto const & node : nodes)
{
if (context && context->labels.contains(&node))
{
if (index > 0) out << '\n';
out << context->labels.at(&node) << ":\n";
}
if (context)
out << " ";
visitor.prelude();
out << index << ": ";
++index;
std::visit(visitor, node.instruction);
if (node.inferred_type)
{
out << " : ";
types::print(out, *node.inferred_type);
}
out << '\n';
}
}
}
void print(std::ostream & out, node_list const & nodes)
{
print_impl(out, nodes, nullptr);
}
void print(std::ostream & out, module_context const & context)
{
print_impl(out, context.nodes, &context);
}
}

View file

@ -3,4 +3,4 @@ file(GLOB_RECURSE PSLANG_JIT_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/source/*.cpp")
add_library(pslang-jit STATIC ${PSLANG_JIT_HEADERS} ${PSLANG_JIT_SOURCES}) add_library(pslang-jit STATIC ${PSLANG_JIT_HEADERS} ${PSLANG_JIT_SOURCES})
target_include_directories(pslang-jit PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include") target_include_directories(pslang-jit PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include")
target_link_libraries(pslang-jit PUBLIC pslang-ast) target_link_libraries(pslang-jit PUBLIC pslang-ast pslang-ir)