From 138bddf18711ca027735b55e7f49cf1ab8eef43a Mon Sep 17 00:00:00 2001 From: lisyarus Date: Sun, 29 Mar 2026 23:22:29 +0300 Subject: [PATCH] Remove old aarch64 compiler and update plans --- .../pslang/jit/arch/aarch64/compiler.hpp | 2 - libs/jit/include/pslang/jit/jit.hpp | 1 - libs/jit/source/arch/aarch64/compiler.cpp | 2148 +++++++---------- libs/jit/source/arch/aarch64/compiler_v2.cpp | 1139 --------- libs/jit/source/jit.cpp | 14 - plans.txt | 14 +- 6 files changed, 935 insertions(+), 2383 deletions(-) delete mode 100644 libs/jit/source/arch/aarch64/compiler_v2.cpp diff --git a/libs/jit/include/pslang/jit/arch/aarch64/compiler.hpp b/libs/jit/include/pslang/jit/arch/aarch64/compiler.hpp index 550cd82..374ebd9 100644 --- a/libs/jit/include/pslang/jit/arch/aarch64/compiler.hpp +++ b/libs/jit/include/pslang/jit/arch/aarch64/compiler.hpp @@ -6,8 +6,6 @@ namespace pslang::jit::aarch64 { - void compile(program_context & context, ast::statement_list_ptr const & statements); - void compile(program_context & pcontext, ir::module_context const & mcontext); } diff --git a/libs/jit/include/pslang/jit/jit.hpp b/libs/jit/include/pslang/jit/jit.hpp index 1db055e..a29e107 100644 --- a/libs/jit/include/pslang/jit/jit.hpp +++ b/libs/jit/include/pslang/jit/jit.hpp @@ -7,7 +7,6 @@ namespace pslang::jit { - void compile(program_context & context, ast::statement_list_ptr const & statements); void compile(program_context & pcontext, ir::module_context const & mcontext); } diff --git a/libs/jit/source/arch/aarch64/compiler.cpp b/libs/jit/source/arch/aarch64/compiler.cpp index e07eaf8..41ab352 100644 --- a/libs/jit/source/arch/aarch64/compiler.cpp +++ b/libs/jit/source/arch/aarch64/compiler.cpp @@ -1,15 +1,12 @@ #include #include -#include -#include -#include +#include +#include +#include +#include +#include #include -#include -#include -#include -#include -#include #include namespace pslang::jit::aarch64 @@ -22,92 +19,32 @@ namespace pslang::jit::aarch64 // of the same type (after struct flattening) struct hfa_data { - types::type_ptr type; + types::type_ptr element_type; std::size_t count; }; struct local_context { + bool use_frame_pointer = true; + + std::unordered_map> struct_hfa; + + std::unordered_map extern_symbols; + std::unordered_map nodes; + std::unordered_map f16_constants; std::unordered_map f32_constants; std::unordered_map f64_constants; - std::unordered_map foreign_address; - - std::unordered_map functions; - - struct struct_data + struct resolve_data { - std::optional hfa = {}; + std::int32_t offset; + ir::node_ref target; }; - std::unordered_map structs; - - struct scope - { - std::unordered_set foreign_functions; - std::unordered_map functions; - std::unordered_map structs; - }; - - std::vector scopes; - - struct resolve_info - { - ast::function_definition const * node; - // Must be 'adr' instruction - std::int32_t instruction_offset; - }; - - std::vector resolve; - - bool is_foreign(std::string const & name) - { - for (auto it = scopes.rbegin(); it != scopes.rend(); ++it) - { - if (it->foreign_functions.contains(name)) - return true; - - if (it->functions.contains(name)) - return false; - - if (it->structs.contains(name)) - return false; - } - return false; - } - - ast::function_definition const * is_function(std::string const & name) - { - for (auto it = scopes.rbegin(); it != scopes.rend(); ++it) - { - if (auto jt = it->functions.find(name); jt != it->functions.end()) - return jt->second; - - if (it->foreign_functions.contains(name)) - return nullptr; - - if (it->structs.contains(name)) - return nullptr; - } - return nullptr; - } - - ast::struct_definition const * is_struct(std::string const & name) - { - for (auto it = scopes.rbegin(); it != scopes.rend(); ++it) - { - if (auto jt = it->structs.find(name); jt != it->structs.end()) - return jt->second; - - if (it->foreign_functions.contains(name)) - return nullptr; - - if (it->functions.contains(name)) - return nullptr; - } - return nullptr; - } + std::vector branch_resolve; + std::vector cbranch_resolve; + std::vector adr_resolve; }; std::uint8_t fp_mode_for(types::type const & type) @@ -119,163 +56,102 @@ namespace pslang::jit::aarch64 return 3; } - bool is_short_circuiting(ast::binary_operation_type type) + std::int32_t fp_size(std::uint8_t mode) { - switch (type) - { - case ast::binary_operation_type::logical_and: - case ast::binary_operation_type::logical_or: - return true; - default: - return false; - } + return 1 << mode; } - // Add all f16, f32 and f64 constants as read-only data entries - // Add extern pointers for all foreign functions as read-only data entries - struct populate_constants_visitor - : ast::const_expression_visitor - , ast::const_statement_visitor - { - using const_expression_visitor::apply; - using const_statement_visitor::apply; + std::optional get_hfa_data(local_context & lcontext, types::type_ptr const & type); + std::optional compute_hfa_data(local_context & lcontext, ast::struct_definition const * node) + { + types::type_ptr type = nullptr; + std::size_t count = 0; + + for (std::size_t i = 0; i < node->fields.size(); ++i) + { + auto const & field = node->fields[i]; + + // NB: recursion must be impossible due to prior checks in type checker + if (auto subdata = get_hfa_data(lcontext, field.inferred_type)) + { + if (type && !types::equal(*type, *subdata->element_type)) + return std::nullopt; + + type = subdata->element_type; + count += subdata->count; + } + else + return std::nullopt; + } + + if (count == 0) + return std::nullopt; + + return hfa_data{type, count}; + } + + std::optional get_hfa_data(local_context & lcontext, types::type_ptr const & type) + { + if (auto struct_type = std::get_if(type.get())) + { + if (auto it = lcontext.struct_hfa.find(struct_type->node); it != lcontext.struct_hfa.end()) + return it->second; + + auto result = compute_hfa_data(lcontext, struct_type->node); + lcontext.struct_hfa[struct_type->node] = result; + return result; + } + else if (auto array_type = std::get_if(type.get())) + { + if (auto subdata = get_hfa_data(lcontext, array_type->element_type)) + return hfa_data{subdata->element_type, subdata->count * array_type->size}; + else + return std::nullopt; + } + else if (types::is_floating_point_type(*type)) + return hfa_data{type, 1}; + else + return std::nullopt; + } + + struct populate_const_data_visitor + { program_context & pcontext; local_context & lcontext; - template - requires(!std::is_floating_point_v) - void apply(ast::primitive_literal_base const &) + template + void apply(Node const & node, types::type_ptr const &) {} - void apply(ast::f16_literal const & node) + void apply(ir::literal const & node, types::type_ptr const &) { - if (!lcontext.f16_constants.contains(node.value.repr)) + if (auto f16_literal = std::get_if(&node.value)) { - lcontext.f16_constants[node.value.repr] = pcontext.code.size(); - push_bytes(node.value.repr); + lcontext.f16_constants[f16_literal->value.repr] = pcontext.code.size(); + push_bytes(f16_literal->value.repr); + } + else if (auto f32_literal = std::get_if(&node.value)) + { + lcontext.f32_constants[f32_literal->value] = pcontext.code.size(); + push_bytes(f32_literal->value); + } + else if (auto f64_literal = std::get_if(&node.value)) + { + lcontext.f32_constants[f64_literal->value] = pcontext.code.size(); + push_bytes(f64_literal->value); } } - void apply(ast::f32_literal const & node) + void apply(ir::extern_symbol const & node, types::type_ptr const &) { - if (!lcontext.f32_constants.contains(node.value)) - { - lcontext.f32_constants[node.value] = pcontext.code.size(); - push_bytes(node.value); - } + std::int32_t offset = pcontext.code.size(); + lcontext.extern_symbols[node.name] = offset; + pcontext.foreign_resolve.push_back({node.name, offset}); + push_bytes(nullptr); } - void apply(ast::f64_literal const & node) - { - if (!lcontext.f64_constants.contains(node.value)) - { - lcontext.f64_constants[node.value] = pcontext.code.size(); - push_bytes(node.value); - } - } - - void apply(ast::identifier const &) - {} - - void apply(ast::unary_operation const & node) - { - apply(*node.arg1); - } - - void apply(ast::binary_operation const & node) - { - apply(*node.arg1); - apply(*node.arg2); - } - - void apply(ast::cast_operation const & node) - { - apply(*node.expression); - } - - void apply(ast::function_call const & node) - { - if (node.function) - apply(*node.function); - for (auto const & argument : node.arguments) - apply(*argument); - } - - void apply(ast::array const & node) - { - for (auto const & element : node.elements) - apply(*element); - } - - void apply(ast::array_access const & node) - { - apply(*node.array); - apply(*node.index); - } - - void apply(ast::field_access const & node) - { - apply(*node.object); - } - - void apply(ast::expression_ptr const & node) - { - apply(*node); - } - - void apply(ast::assignment const & node) - { - apply(*node.lhs); - apply(*node.rhs); - } - - void apply(ast::variable_declaration const & node) - { - apply(*node.initializer); - } - - void apply(ast::if_chain const & node) - { - for (auto const & block : node.blocks) - { - if (block.condition) - apply(*block.condition); - apply(*block.statements); - } - } - - void apply(ast::while_block const & node) - { - apply(*node.condition); - apply(*node.statements); - } - - void apply(ast::function_definition const & node) - { - apply(*node.statements); - } - - void apply(ast::foreign_function_declaration const & foreign_function_declaration) - { - if (!lcontext.foreign_address.contains(foreign_function_declaration.name)) - { - lcontext.foreign_address[foreign_function_declaration.name] = pcontext.code.size(); - push_bytes(nullptr); - } - } - - void apply(ast::return_statement const & node) - { - if (node.value) - apply(*node.value); - } - - void apply(ast::struct_definition const &) - {} - private: - template void push_bytes(T const & value) { @@ -285,91 +161,74 @@ namespace pslang::jit::aarch64 } }; - std::optional get_hfa_data(ast::struct_definition const & node, local_context & lcontext) + // Set register @reg to -1 (all bits = 1) + void set_m1(instruction_builder & builder, std::uint8_t reg) { - if (auto it = lcontext.structs.find(&node); it != lcontext.structs.end()) - return it->second.hfa; - - types::type_ptr type = nullptr; - std::size_t count = 0; - - for (auto const & field : node.fields) - { - if (types::is_builtin_type(*field.inferred_type)) - { - if (!types::is_floating_point_type(*field.inferred_type)) - return std::nullopt; - - if (type && !types::equal(*type, *field.inferred_type)) - return std::nullopt; - - type = field.inferred_type; - ++count; - } - else if (auto struct_type = std::get_if(field.inferred_type.get())) - { - // NB: recursion must be impossible due to prior checks in type checker - if (auto subdata = get_hfa_data(*struct_type->node, lcontext)) - { - if (type && !types::equal(*type, *subdata->type)) - return std::nullopt; - - type = subdata->type; - count += subdata->count; - } - else - return std::nullopt; - } - else - return std::nullopt; - } - - if (count <= 4) - return hfa_data{type, count}; - - return std::nullopt; + builder.or_not_reg(31, 31, reg); } - // 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 + struct literal_visitor { + program_context & pcontext; local_context & lcontext; + instruction_builder & builder; - 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_chain const &) {} - - void apply(ast::while_block const &) {} - - void apply(ast::function_definition const & node) + void operator()(ast::bool_literal const & node) { - lcontext.scopes.back().functions[node.name] = &node; + if (node.value) + set_m1(builder, 0); + else + builder.movz(0, 0); } - void apply(ast::foreign_function_declaration const & node) + template + requires(std::is_integral_v && !std::is_same_v) + void operator()(ast::primitive_literal_base const & node) { - lcontext.scopes.back().foreign_functions.insert(node.name); - } - - void apply(ast::return_statement const &) {} - - void apply(ast::struct_definition const & node) - { - lcontext.scopes.back().structs[node.name] = &node; - if (!lcontext.structs.contains(&node)) + for (std::size_t i = 0; i < sizeof(T); i += 2) { - // NB: make sure not to add struct to lcontext.structs before computing hfa data - auto hfa = get_hfa_data(node, lcontext); - lcontext.structs[&node].hfa = hfa; + if (i == 0) + { + builder.movz(0, std::uint64_t(node.value)); + } + else + { + auto val = std::uint16_t(std::uint64_t(node.value) >> (i * 8)); + if (val != 0) + builder.movk(0, val, i / 2); + } } + + if (sizeof(T) < 8) + { + if constexpr (std::is_signed_v) + { + if (node.value < 0) + builder.sbfm(0, 0, sizeof(T) * 8); + } + } + } + + void operator()(ast::f16_literal const & node) + { + auto offset = lcontext.f16_constants.at(node.value.repr); + std::int32_t current = pcontext.code.size(); + builder.ldr_fp_pc(0, 0, (offset - current) / 4); + builder.fcvt(0, 0b10, 0, 0b01); + } + + void operator()(ast::f32_literal const & node) + { + auto offset = lcontext.f32_constants.at(node.value); + std::int32_t current = pcontext.code.size(); + builder.ldr_fp_pc(0, 0, (offset - current) / 4); + } + + void operator()(ast::f64_literal const & node) + { + auto offset = lcontext.f64_constants.at(node.value); + std::int32_t current = pcontext.code.size(); + builder.ldr_fp_pc(0, 1, (offset - current) / 4); } }; @@ -382,6 +241,11 @@ namespace pslang::jit::aarch64 std::uint8_t reg; void apply(types::bool_type const &) + { + builder.ubfm(reg, reg, 8); + } + + void apply(types::f16_type const &) {} void apply(types::f32_type const &) @@ -416,864 +280,778 @@ namespace pslang::jit::aarch64 } }; - // Compile a single function and store the entry point offset - // in local_context - struct compile_function_visitor - : ast::const_statement_visitor - , ast::const_expression_visitor + struct compile_visitor { - using const_statement_visitor::apply; - using const_expression_visitor::apply; - program_context & pcontext; + ir::module_context const & mcontext; local_context & lcontext; - instruction_builder builder{pcontext.code}; + instruction_builder & builder; - // Difference between initial stack pointer at function enter - // and current virtual stack pointer value. The actual stack pointer - // value is rounded down to a multiple of 16 - std::uint32_t stack_offset = 0; + std::vector argument_position; + std::unordered_map stack_position; + std::int32_t stack_size = 0; + bool return_value_is_large_struct = false; - struct variable_data + void apply(ir::node_ref, ir::label const &, types::type_ptr const &) + {} + + void apply(ir::node_ref it, ir::literal const & node, types::type_ptr const & type) { - // Difference between initial stack pointer at function enter - // and the variable address - // Must be a multiple of 16 - std::uint32_t frame_offset; - }; - - struct scope - { - std::unordered_map variables = {}; - - // Difference between initial virtual stack pointer at scope enter - // and current virtual stack pointer value - std::uint32_t stack_offset = 0; - }; - - std::vector scopes; - - template - void apply(Node const &) - { - throw std::runtime_error(std::string("compile_function_visitor is not implemented for ") + typeid(Node).name()); + std::visit(literal_visitor{pcontext, lcontext, builder}, node.value); + if (types::is_integer_like_type(*type)) + store(it, 0); + else if (types::is_floating_point_type(*type)) + store_fp(it, 0, fp_mode_for(*type)); } - void apply(ast::bool_literal const & node) + void apply(ir::node_ref it, ir::alloc const & node, types::type_ptr const &) { - if (node.value) - set_m1(0); - else - builder.movz(0, 0); + // Nothing to do: alloc just allocates a node of a struct type, + // but we already allocated stack space for it } - template - requires(std::is_integral_v && !std::is_same_v) - void apply(ast::primitive_literal_base const & node) + void apply(ir::node_ref it, ir::copy const & node, types::type_ptr const & type) { - for (std::size_t i = 0; i < sizeof(T); i += 2) + // TODO: array/array element copy? + auto size = ast::type_size(*type); + auto dst_offset = stack_size - stack_position.at(it); + + auto src_type = node.source->inferred_type; + auto src_offset = stack_size - stack_position.at(node.source); + for (auto field_id : node.path) { - if (i == 0) - { - builder.movz(0, std::uint64_t(node.value)); - } - else - { - auto val = std::uint16_t(std::uint64_t(node.value) >> (i * 8)); - if (val != 0) - builder.movk(0, val, i / 2); - } + auto const & field = std::get(*src_type).node->fields[field_id]; + src_type = field.inferred_type; + src_offset += field.layout.offset; } - if (sizeof(T) < 8) - { - if constexpr (std::is_signed_v) - { - if (node.value < 0) - builder.sbfm(0, 0, sizeof(T) * 8); - } - } + copy_memory(31, src_offset, 31, dst_offset, size, 0); } - void apply(ast::f16_literal const & node) + void apply(ir::node_ref it, ir::load const & node, types::type_ptr const & type) { - auto offset = lcontext.f16_constants.at(node.value.repr); - std::int32_t current = pcontext.code.size(); - builder.ldr_fp_pc(0, 0, (offset - current) / 4); - builder.fcvt(0, 0b10, 0, 0b01); + // TODO: array/array element load? + load(node.ptr, 0); + auto size = ast::type_size(*type); + auto dst_offset = stack_size - stack_position.at(it); + copy_memory(0, 0, 31, dst_offset, size, 1); } - void apply(ast::f32_literal const & node) + void apply(ir::node_ref, ir::store const & node, types::type_ptr const & type) { - auto offset = lcontext.f32_constants.at(node.value); - std::int32_t current = pcontext.code.size(); - builder.ldr_fp_pc(0, 0, (offset - current) / 4); + // TODO: array/array element store? + load(node.ptr, 0); + auto size = ast::type_size(*type); + std::int32_t src_offset = stack_size - stack_position.at(node.value); + copy_memory(31, src_offset, 0, 0, size, 1); } - void apply(ast::f64_literal const & node) - { - auto offset = lcontext.f64_constants.at(node.value); - std::int32_t current = pcontext.code.size(); - builder.ldr_fp_pc(0, 1, (offset - current) / 4); - } - - void apply(ast::identifier const & node) - { - for (auto it = scopes.rbegin(); it != scopes.rend(); ++it) - { - if (node.variable_node && it->variables.contains(node.variable_node)) - { - auto jt = it->variables.find(node.variable_node); - if (auto struct_type = std::get_if(node.inferred_type.get())) - { - std::size_t stack_size = ((struct_type->node->layout.size + 15) / 16) * 16; - builder.sub_imm(31, 31, stack_size); - stack_offset += stack_size; - scopes.back().stack_offset += stack_size; - std::size_t variable_offset = stack_offset - jt->second.frame_offset; - for (std::size_t offset = 0; offset < stack_size; offset += 16) - { - builder.ldr(0, 31, (variable_offset + offset) / 8); - builder.ldr(1, 31, (variable_offset + offset) / 8 + 1); - builder.str(0, 31, offset / 8); - builder.str(1, 31, offset / 8 + 1); - } - } - else if (types::is_unit_type(*node.inferred_type)) - {} - else if (types::is_floating_point_type(*node.inferred_type)) - builder.ldr_fp(0, fp_mode_for(*node.inferred_type), 31, (stack_offset - jt->second.frame_offset) / type_size(*node.inferred_type)); - else - builder.ldr(0, 31, (stack_offset - jt->second.frame_offset) / 8); - return; - } - } - - if (lcontext.is_foreign(node.name)) - { - builder.ldr_pc(0, (lcontext.foreign_address.at(node.name) - (std::int32_t)pcontext.code.size()) / 4); - } - else if (auto function_node = lcontext.is_function(node.name)) - { - lcontext.resolve.push_back({function_node, (std::int32_t)pcontext.code.size()}); - builder.adr(0, 0); - } - else - { - throw std::runtime_error("unknown identifier \"" + node.name + "\""); - } - } - - void apply(ast::unary_operation const & node) + void apply(ir::node_ref it, ir::unary_operation const & node, types::type_ptr const & type) { switch (node.type) { - case ast::unary_operation_type::negation: - apply(*node.arg1); - if (types::is_integer_type(*node.inferred_type)) - { - builder.sub_reg(31, 0, 0); - extend(0, node.inferred_type); - } - else if (types::is_floating_point_type(*node.inferred_type)) - { - builder.fneg(0, fp_mode_for(*node.inferred_type), 0); - } - break; - case ast::unary_operation_type::logical_not: - apply(*node.arg1); - builder.or_not_reg(31, 0, 0); - if (types::is_integer_type(*node.inferred_type)) - extend(0, node.inferred_type); - break; - case ast::unary_operation_type::address_of: - case ast::unary_operation_type::mutable_address_of: - case ast::unary_operation_type::dereference: - throw std::runtime_error("Not implemented"); - } - } - - void apply(ast::binary_operation const & node) - { - auto arg1_type = ast::get_type(*node.arg1); - bool const is_fp = types::is_floating_point_type(*arg1_type); - std::uint8_t const fp_mode = fp_mode_for(*arg1_type); - - apply(*node.arg1); - - if (!is_short_circuiting(node.type)) - { - if (is_fp) + case ast::unary_operation_type::negation: + if (types::is_integer_type(*type)) { - push_fp(0, fp_mode); - apply(*node.arg2); - pop_fp(1, fp_mode); - } - else - { - push(0); - apply(*node.arg2); - pop(1); - } - } - - switch (node.type) - { - case ast::binary_operation_type::addition: - if (is_fp) - builder.fadd(1, 0, fp_mode, 0); - else - { - builder.add_reg(1, 0, 0); - extend(0, node.inferred_type); - } - break; - case ast::binary_operation_type::subtraction: - if (is_fp) - builder.fsub(1, 0, fp_mode, 0); - else - { - builder.sub_reg(1, 0, 0); - extend(0, node.inferred_type); - } - break; - case ast::binary_operation_type::multiplication: - if (is_fp) - builder.fmul(1, 0, fp_mode, 0); - else - { - builder.mul_reg(1, 0, 0); - extend(0, node.inferred_type); - } - break; - case ast::binary_operation_type::division: - if (is_fp) - builder.fdiv(1, 0, fp_mode, 0); - else - { - if (types::is_signed_integer_type(*node.inferred_type)) - builder.sdiv_reg(1, 0, 0); - else - builder.udiv_reg(1, 0, 0); - extend(0, node.inferred_type); - } - break; - case ast::binary_operation_type::remainder: - if (types::is_signed_integer_type(*node.inferred_type)) - { - builder.sdiv_reg(1, 0, 2); - builder.mul_reg(0, 2, 0); - builder.sub_reg(1, 0, 0); - } - else if (types::is_unsigned_integer_type(*node.inferred_type)) - { - builder.udiv_reg(1, 0, 2); - builder.mul_reg(0, 2, 0); - builder.sub_reg(1, 0, 0); - } - break; - case ast::binary_operation_type::binary_and: - builder.and_reg(1, 0, 0); - break; - case ast::binary_operation_type::logical_and: - { - std::int32_t start = pcontext.code.size(); - builder.cbz(0, 0); - push(0); - apply(*node.arg2); - pop(1); - builder.and_reg(1, 0, 0); - std::int32_t end = pcontext.code.size(); - builder.cb_inject(pcontext.code.data() + start, (end - start) / 4); - } - break; - case ast::binary_operation_type::binary_or: - builder.or_reg(1, 0, 0); - break; - case ast::binary_operation_type::logical_or: - { - set_m1(1); - extend(1, arg1_type); - builder.xor_reg(0, 1, 1); - std::int32_t start = pcontext.code.size(); - builder.cbz(1, 0); - push(0); - apply(*node.arg2); - pop(1); - builder.or_reg(1, 0, 0); - std::int32_t end = pcontext.code.size(); - builder.cb_inject(pcontext.code.data() + start, (end - start) / 4); - } - break; - case ast::binary_operation_type::logical_xor: - builder.xor_reg(1, 0, 0); - break; - case ast::binary_operation_type::equals: - if (is_fp) - { - builder.fcmp(1, 0, fp_mode); - builder.csetm(0, 0b0000); - } - else - { - builder.cmp_reg(1, 0); - builder.csetm(0, 0b0000); - } - break; - case ast::binary_operation_type::not_equals: - if (is_fp) - { - builder.fcmp(1, 0, fp_mode); - builder.csetm(0, 0b0001); - } - else - { - builder.cmp_reg(1, 0); - builder.csetm(0, 0b0001); - } - break; - case ast::binary_operation_type::less: - if (is_fp) - { - builder.fcmp(1, 0, fp_mode); - builder.csetm(0, 0b0100); - } - else - { - builder.cmp_reg(0, 1); - if (types::is_bool_type(*ast::get_type(*node.arg1)) || types::is_unsigned_integer_type(*ast::get_type(*node.arg1))) - builder.csetm(0, 0b1000); - else - builder.csetm(0, 0b1100); - } - break; - case ast::binary_operation_type::greater: - if (is_fp) - { - builder.fcmp(0, 1, fp_mode); - builder.csetm(0, 0b0100); - } - else - { - builder.cmp_reg(1, 0); - if (types::is_bool_type(*ast::get_type(*node.arg1)) || types::is_unsigned_integer_type(*ast::get_type(*node.arg1))) - builder.csetm(0, 0b1000); - else - builder.csetm(0, 0b1100); - } - break; - case ast::binary_operation_type::less_equals: - if (is_fp) - { - builder.fcmp(1, 0, fp_mode); - builder.csetm(0, 0b1001); - } - else - { - builder.cmp_reg(1, 0); - if (types::is_bool_type(*ast::get_type(*node.arg1)) || types::is_unsigned_integer_type(*ast::get_type(*node.arg1))) - builder.csetm(0, 0b1001); - else - builder.csetm(0, 0b1101); - } - break; - case ast::binary_operation_type::greater_equals: - if (is_fp) - { - builder.fcmp(0, 1, fp_mode); - builder.csetm(0, 0b1001); - } - else - { - builder.cmp_reg(0, 1); - if (types::is_bool_type(*ast::get_type(*node.arg1)) || types::is_unsigned_integer_type(*ast::get_type(*node.arg1))) - builder.csetm(0, 0b1001); - else - builder.csetm(0, 0b1101); - } - break; - default: - { - std::ostringstream os; - os << "binary operation " << node.type << " is not implemented"; - throw std::runtime_error(os.str()); - } - } - } - - void apply(ast::cast_operation const & node) - { - auto src_type = ast::get_type(*node.expression); - auto dst_type = node.inferred_type; - - apply(*node.expression); - - if (types::equal(*src_type, *dst_type)) - return; - - if (types::is_integer_type(*src_type)) - { - if (types::is_integer_type(*dst_type)) - { - extend(0, dst_type); - } - else if (types::is_floating_point_type(*dst_type)) - { - auto dst_mode = fp_mode_for(*dst_type); - if (types::is_signed_integer_type(*src_type)) - { - builder.fmov(0, 0, 3, 1); - builder.scvtf(0, 0, 3); - if (dst_mode != 3) - builder.fcvt(0, 3, 0, dst_mode); - } - else if (types::is_unsigned_integer_type(*src_type)) - { - builder.fmov(0, 0, 3, 1); - builder.ucvtf(0, 0, 3); - if (dst_mode != 3) - builder.fcvt(0, 3, 0, dst_mode); - } - } - } - else if (types::is_floating_point_type(*src_type)) - { - auto src_mode = fp_mode_for(*src_type); - if (types::is_integer_type(*dst_type)) - { - if (types::is_signed_integer_type(*dst_type)) - { - builder.fcvtns(0, 0, src_mode); - extend(0, dst_type); - } - else if (types::is_unsigned_integer_type(*dst_type)) - { - builder.fcvtnu(0, 0, src_mode); - extend(0, dst_type); - } - } - else if (types::is_floating_point_type(*dst_type)) - { - auto dst_mode = fp_mode_for(*dst_type); - builder.fcvt(0, src_mode, 0, dst_mode); - } - } - } - - void apply(ast::function_call const & node) - { - if (node.function) - { - apply(*node.function); - push(0); - - for (std::size_t i = node.arguments.size(); i --> 0;) - { - auto const & arg = node.arguments[i]; - apply(*arg); - auto type = ast::get_type(*arg); - if (types::is_bool_type(*type) || types::is_integer_type(*type)) - { - push(0); - } - else if (types::is_floating_point_type(*type)) - { - push_fp(0, fp_mode_for(*type)); - } - } - - std::uint8_t reg = 0; - std::uint8_t fp_reg = 0; - for (auto const & arg : node.arguments) - { - auto type = ast::get_type(*arg); - if (types::is_bool_type(*type) || types::is_integer_type(*type)) - { - pop(reg); - ++reg; - } - else if (types::is_floating_point_type(*type)) - { - pop_fp(fp_reg, fp_mode_for(*type)); - ++fp_reg; - } - } - - pop(reg); - push(30); - builder.bl_reg(reg); - pop(30); - } - else // if (node.type) - { - if (types::is_unit_type(*node.inferred_type)) - { - // Do nothing - } - else if (types::is_bool_type(*node.inferred_type) || types::is_integer_type(*node.inferred_type) || types::is_function_type(*node.inferred_type)) - { - builder.xor_reg(0, 0, 0); - } - else if (types::is_floating_point_type(*node.inferred_type)) - { - builder.xor_reg(0, 0, 0); - builder.fmov(0, 0, fp_mode_for(*node.inferred_type), 1); - } - else if (auto struct_type = std::get_if(node.inferred_type.get())) - { - auto & struct_node = *struct_type->node; - - // Allocate stack space for the struct - std::size_t stack_size = ((struct_node.layout.size + 15) / 16) * 16; - auto offset = stack_offset; - stack_offset += stack_size; - scopes.back().stack_offset += stack_size; - builder.sub_imm(31, 31, stack_size); - - // Evaluate each field of the struct (i.e. each constructor argument) - // and copy it to the corresponding place in the struct - for (std::size_t i = 0; i < node.arguments.size(); ++i) - { - auto type = ast::get_type(*node.arguments[i]); - apply(*node.arguments[i]); - - if (std::get_if(type.get())) - { - // TODO: struct field - throw std::runtime_error("Not implemented"); - } - else if (types::is_floating_point_type(*type)) - { - builder.stur_fp(0, fp_mode_for(*type), 31, struct_node.fields[i].layout.offset); - } - else - { - auto size = types::type_size(*type); - - if (size == 1) - builder.sturb(0, 31, struct_node.fields[i].layout.offset); - else if (size == 2) - builder.sturh(0, 31, struct_node.fields[i].layout.offset); - else if (size == 4) - builder.sturw(0, 31, struct_node.fields[i].layout.offset); - else if (size == 8) - builder.stur(0, 31, struct_node.fields[i].layout.offset); - } - } - } - } - } - - void apply(ast::field_access const & node) - { - auto object_type = get_type(*node.object); - if (auto struct_type = std::get_if(object_type.get())) - { - auto & struct_node = *struct_type->node; - - std::optional field_id; - for (std::size_t i = 0; i < struct_node.fields.size(); ++i) - { - if (struct_node.fields[i].name == node.field_name) - { - field_id = i; - break; - } - } - - if (!field_id) - throw std::runtime_error("Unknown field \"" + node.field_name + "\" in struct \"" + struct_node.name + "\""); - - apply(*node.object); - - auto stack_size = ((struct_node.layout.size + 15) / 16) * 16; - - auto const & field = struct_node.fields[*field_id]; - if (types::is_unit_type(*field.inferred_type)) - {} - else if (types::is_floating_point_type(*field.inferred_type)) - { - builder.ldur_fp(0, fp_mode_for(*field.inferred_type), 31, field.layout.offset); - - builder.add_imm(31, 31, stack_size); - stack_offset -= stack_size; - scopes.back().stack_offset -= stack_size; - } - else if (types::is_bool_type(*field.inferred_type) || types::is_integer_type(*field.inferred_type) || types::is_function_type(*field.inferred_type)) - { - auto size = types::type_size(*field.inferred_type); - if (size == 1) - builder.ldurb(0, 31, field.layout.offset); - else if (size == 2) - builder.ldurh(0, 31, field.layout.offset); - else if (size == 4) - builder.ldurw(0, 31, field.layout.offset); - else if (size == 8) - builder.ldur(0, 31, field.layout.offset); - - builder.add_imm(31, 31, stack_size); - stack_offset -= stack_size; - scopes.back().stack_offset -= stack_size; - } - else if (auto struct_type = std::get_if(field.inferred_type.get())) - { - // TODO: copy the struct-typed field on stack, overriding - // the struct itself, and update the stack offset - throw std::runtime_error("Not implemented"); - } - - return; - } - - throw std::runtime_error("Unknown object in field access"); - } - - void apply(ast::expression_ptr const & node) - { - auto stack_offset_before = stack_offset; - apply(*node); - - // Restore stack offset in case the expression evaluated to a struct - // (in which case the struct would be placed on the stack) - auto stack_delta = stack_offset - stack_offset_before; - if (stack_delta > 0) - { - builder.add_imm(31, 31, stack_delta); - stack_offset -= stack_delta; - scopes.back().stack_offset -= stack_delta; - } - } - - void apply(ast::assignment const & node) - { - auto frame_offset = lvalue_offset(node.lhs); - - apply(*node.rhs); - auto type = ast::get_type(*node.rhs); - if (types::is_unit_type(*type)) - {} - else if (types::is_floating_point_type(*type)) - builder.str_fp(0, fp_mode_for(*type), 31, (stack_offset - frame_offset) / type_size(*type)); - else if (types::is_bool_type(*type) || types::is_integer_type(*type) || types::is_function_type(*type)) - { - auto size = types::type_size(*type); - if (size == 1) - builder.sturb(0, 31, stack_offset - frame_offset); - else if (size == 2) - builder.sturh(0, 31, stack_offset - frame_offset); - else if (size == 4) - builder.sturw(0, 31, stack_offset - frame_offset); - else if (size == 8) - builder.stur(0, 31, stack_offset - frame_offset); - } - else if (auto struct_type = std::get_if(type.get())) - { - // TODO: whole-struct assignment - throw std::runtime_error("Not implemented"); - } - } - - void apply(ast::variable_declaration const & node) - { - apply(*node.initializer); - auto type = ast::get_type(*node.initializer); - if (std::get_if(type.get())) - { - // Nothing to be done: the struct is already on the stack - // Just record the stack offset as variable location - } - else if (types::is_floating_point_type(*type)) - push_fp(0, fp_mode_for(*type)); - else if (types::is_unit_type(*type)) - { - // Nothing to be done: unit type has zero size - // Its stack position is recorded but de facto unused - } - else - push(0); - scopes.back().variables[&node] = {.frame_offset = stack_offset}; - } - - void apply(ast::if_chain const & node) - { - std::vector branch_to_end; - for (std::size_t i = 0; i < node.blocks.size(); ++i) - { - auto const & block = node.blocks[i]; - - std::optional branch_skip; - if (block.condition) - { - apply(*block.condition); - branch_skip = pcontext.code.size(); - builder.cbz(0, 0); - } - - scopes.emplace_back(); - apply(*block.statements); - scope_cleanup(); - scopes.pop_back(); - - if (i + 1 < node.blocks.size()) - { - branch_to_end.push_back(pcontext.code.size()); - builder.b(0); - } - - if (branch_skip) - { - auto branch_offset = pcontext.code.size() - *branch_skip; - builder.cb_inject(pcontext.code.data() + *branch_skip, branch_offset / 4); - } - } - - auto end = pcontext.code.size(); - for (auto instruction : branch_to_end) - { - auto delta = end - instruction; - builder.b_inject(pcontext.code.data() + instruction, delta / 4); - } - } - - void apply(ast::while_block const & node) - { - std::int32_t start = pcontext.code.size(); - apply(*node.condition); - std::int32_t skip = pcontext.code.size(); - builder.cbz(0, 0); - - scopes.emplace_back(); - apply(*node.statements); - scope_cleanup(); - scopes.pop_back(); - - std::int32_t loop = pcontext.code.size(); - builder.b(0); - std::int32_t end = pcontext.code.size(); - - builder.cb_inject(pcontext.code.data() + skip, (end - skip) / 4); - builder.b_inject(pcontext.code.data() + loop, (start - loop) / 4); - } - - void apply(ast::return_statement const & node) - { - // TODO: struct return value - if (node.value) - apply(*node.value); - do_return(); - } - - void apply(ast::function_definition const &) - { - // Must be handled prior to that in populate_symbols_visitor - } - - void apply(ast::foreign_function_declaration const &) - { - // Must be handled prior to that in populate_symbols_visitor - } - - void apply(ast::struct_definition const &) - { - // Must be handled prior to that in populate_symbols_visitor - } - - void apply(ast::statement_list const & node) - { - lcontext.scopes.emplace_back(); - populate_symbols_visitor{{}, lcontext}.apply(node); - for (auto const & statement : node.statements) - apply(*statement); - lcontext.scopes.pop_back(); - } - - void do_apply(ast::function_definition const & node) - { - lcontext.functions[&node] = pcontext.code.size(); - - // TODO: struct arguments - scopes.emplace_back(); - - std::uint8_t reg = 0; - std::uint8_t fp_reg = 0; - - for (auto const & argument : node.arguments) - { - auto type = ast::get_type(*argument.type); - if (types::is_bool_type(*type)) - { - builder.tst(reg, reg); - builder.csetm(reg, 0b0001); - push(reg); - ++reg; - } - else if (types::is_integer_type(*type)) - { - extend(reg, type); - push(reg); - ++reg; + load(node.arg1, 0); + builder.sub_reg(31, 0, 0); + store(it, 0); } else if (types::is_floating_point_type(*type)) { auto mode = fp_mode_for(*type); - push_fp(fp_reg, mode); - ++fp_reg; + load_fp(node.arg1, 0, mode); + builder.fneg(0, mode, 0); + store_fp(it, 0, mode); } - scopes.back().variables[&argument] = {.frame_offset = stack_offset}; + break; + case ast::unary_operation_type::logical_not: + load(node.arg1, 0); + builder.or_not_reg(31, 0, 0); + store(it, 0); + break; + case ast::unary_operation_type::address_of: + case ast::unary_operation_type::mutable_address_of: + builder.add_imm(31, 0, stack_size - stack_position.at(node.arg1)); + store(it, 0); + break; + case ast::unary_operation_type::dereference: + throw std::runtime_error("Dereference operator mush not be present in compiled IR"); } - - apply(*node.statements); - if (node.statements->statements.empty() || !std::get_if(node.statements->statements.back().get())) - if (types::equal(*ast::get_type(*node.return_type), types::unit_type{})) - do_return(); - - scopes.pop_back(); } - void do_return() + void apply(ir::node_ref it, ir::binary_operation const & node, types::type_ptr const & type) { - if (stack_offset > 0) - builder.add_imm(31, 31, stack_offset); + auto arg1_type = node.arg1->inferred_type; + bool const is_fp = types::is_floating_point_type(*arg1_type); + bool const result_is_fp = types::is_floating_point_type(*type); + std::uint8_t const fp_mode = fp_mode_for(*arg1_type); + + if (is_fp) + { + load_fp(node.arg1, 0, fp_mode); + load_fp(node.arg2, 1, fp_mode); + } + else + { + load(node.arg1, 0); + load(node.arg2, 1); + } + + switch (node.type) + { + case ast::binary_operation_type::addition: + if (is_fp) + builder.fadd(0, 1, fp_mode, 0); + else + { + builder.add_reg(0, 1, 0); + } + break; + case ast::binary_operation_type::subtraction: + if (is_fp) + builder.fsub(0, 1, fp_mode, 0); + else + { + builder.sub_reg(0, 1, 0); + } + break; + case ast::binary_operation_type::multiplication: + if (is_fp) + builder.fmul(0, 1, fp_mode, 0); + else + { + builder.mul_reg(0, 1, 0); + } + break; + case ast::binary_operation_type::division: + if (is_fp) + builder.fdiv(0, 1, fp_mode, 0); + else + { + extend(0, type); + extend(1, type); + if (types::is_signed_integer_type(*type)) + builder.sdiv_reg(0, 1, 0); + else + builder.udiv_reg(0, 1, 0); + } + break; + case ast::binary_operation_type::remainder: + extend(0, type); + extend(1, type); + if (types::is_signed_integer_type(*type)) + { + builder.sdiv_reg(0, 1, 2); + builder.mul_reg(1, 2, 1); + builder.sub_reg(0, 1, 0); + } + else if (types::is_unsigned_integer_type(*type)) + { + builder.udiv_reg(0, 1, 2); + builder.mul_reg(1, 2, 1); + builder.sub_reg(0, 1, 0); + } + break; + case ast::binary_operation_type::binary_and: + builder.and_reg(0, 1, 0); + break; + case ast::binary_operation_type::logical_and: + throw std::runtime_error("Short-circuiting operators must have been unwrapped in IR compiler"); + case ast::binary_operation_type::binary_or: + builder.or_reg(0, 1, 0); + break; + case ast::binary_operation_type::logical_or: + throw std::runtime_error("Short-circuiting operators must have been unwrapped in IR compiler"); + case ast::binary_operation_type::logical_xor: + builder.xor_reg(0, 1, 0); + break; + case ast::binary_operation_type::equals: + if (is_fp) + { + builder.fcmp(0, 1, fp_mode); + builder.csetm(0, 0b0000); + } + else + { + extend(0, node.arg1->inferred_type); + extend(1, node.arg2->inferred_type); + builder.cmp_reg(0, 1); + builder.csetm(0, 0b0000); + } + break; + case ast::binary_operation_type::not_equals: + if (is_fp) + { + builder.fcmp(0, 1, fp_mode); + builder.csetm(0, 0b0001); + } + else + { + extend(0, node.arg1->inferred_type); + extend(1, node.arg2->inferred_type); + builder.cmp_reg(0, 1); + builder.csetm(0, 0b0001); + } + break; + case ast::binary_operation_type::less: + if (is_fp) + { + builder.fcmp(0, 1, fp_mode); + builder.csetm(0, 0b0100); + } + else + { + extend(0, node.arg1->inferred_type); + extend(1, node.arg2->inferred_type); + builder.cmp_reg(1, 0); + if (types::is_bool_type(*node.arg1->inferred_type) || types::is_unsigned_integer_type(*node.arg1->inferred_type)) + builder.csetm(0, 0b1000); + else + builder.csetm(0, 0b1100); + } + break; + case ast::binary_operation_type::greater: + if (is_fp) + { + builder.fcmp(1, 0, fp_mode); + builder.csetm(0, 0b0100); + } + else + { + extend(0, node.arg1->inferred_type); + extend(1, node.arg2->inferred_type); + builder.cmp_reg(0, 1); + if (types::is_bool_type(*node.arg1->inferred_type) || types::is_unsigned_integer_type(*node.arg1->inferred_type)) + builder.csetm(0, 0b1000); + else + builder.csetm(0, 0b1100); + } + break; + case ast::binary_operation_type::less_equals: + if (is_fp) + { + builder.fcmp(0, 1, fp_mode); + builder.csetm(0, 0b1001); + } + else + { + extend(0, node.arg1->inferred_type); + extend(1, node.arg2->inferred_type); + builder.cmp_reg(0, 1); + if (types::is_bool_type(*node.arg1->inferred_type) || types::is_unsigned_integer_type(*node.arg1->inferred_type)) + builder.csetm(0, 0b1001); + else + builder.csetm(0, 0b1101); + } + break; + case ast::binary_operation_type::greater_equals: + if (is_fp) + { + builder.fcmp(1, 0, fp_mode); + builder.csetm(0, 0b1001); + } + else + { + extend(0, node.arg1->inferred_type); + extend(1, node.arg2->inferred_type); + builder.cmp_reg(1, 0); + if (types::is_bool_type(*node.arg1->inferred_type) || types::is_unsigned_integer_type(*node.arg1->inferred_type)) + builder.csetm(0, 0b1001); + else + builder.csetm(0, 0b1101); + } + break; + default: + { + std::ostringstream os; + os << "binary operation " << node.type << " is not implemented"; + throw std::runtime_error(os.str()); + } + } + + if (result_is_fp) + store_fp(it, 0, fp_mode); + else + store(it, 0); + } + + void apply(ir::node_ref it, ir::cast_operation const & node, types::type_ptr const &) + { + auto src_type = node.arg1->inferred_type; + auto dst_type = node.target_type; + + if (types::equal(*src_type, *dst_type) || (types::is_pointer_type(*src_type) && types::is_pointer_type(*dst_type))) + { + load(node.arg1, 0); + store(it, 0); + return; + } + + if (auto array_type = std::get_if(src_type.get())) + { + if (auto pointer_type = std::get_if(dst_type.get())) + { + if (types::equal(*array_type->element_type, *pointer_type->referenced_type)) + { + std::int32_t offset = stack_size - stack_position.at(node.arg1); + builder.add_imm(31, 0, offset); + store(it, 0); + return; + } + } + } + + if (types::is_numeric_type(*src_type) && types::is_numeric_type(*dst_type)) + { + if (types::is_integer_type(*src_type)) + { + load(node.arg1, 0); + if (types::is_integer_type(*dst_type)) + { + extend(0, dst_type); + } + else if (types::is_floating_point_type(*dst_type)) + { + auto dst_mode = fp_mode_for(*dst_type); + if (types::is_signed_integer_type(*src_type)) + { + builder.fmov(0, 0, 3, 1); + builder.scvtf(0, 0, 3); + if (dst_mode != 3) + builder.fcvt(0, 3, 0, dst_mode); + } + else if (types::is_unsigned_integer_type(*src_type)) + { + builder.fmov(0, 0, 3, 1); + builder.ucvtf(0, 0, 3); + if (dst_mode != 3) + builder.fcvt(0, 3, 0, dst_mode); + } + } + } + else if (types::is_floating_point_type(*src_type)) + { + auto src_mode = fp_mode_for(*src_type); + if (types::is_integer_type(*dst_type)) + { + if (types::is_signed_integer_type(*dst_type)) + { + builder.fcvtns(0, 0, src_mode); + extend(0, dst_type); + } + else if (types::is_unsigned_integer_type(*dst_type)) + { + builder.fcvtnu(0, 0, src_mode); + extend(0, dst_type); + } + } + else if (types::is_floating_point_type(*dst_type)) + { + auto dst_mode = fp_mode_for(*dst_type); + builder.fcvt(0, src_mode, 0, dst_mode); + } + } + + if (types::is_integer_type(*dst_type)) + { + store(it, 0); + } + else if (types::is_floating_point_type(*dst_type)) + { + store_fp(it, 0, fp_mode_for(*dst_type)); + } + + return; + } + + throw std::runtime_error("Unknown types for cast instruction"); + } + + void apply(ir::node_ref it, ir::argument const & node, types::type_ptr const & type) + { + // Nothing to do: arguments already pushed on stack in function preamble + } + + void apply(ir::node_ref it, ir::instruction_address const & node, types::type_ptr const &) + { + lcontext.adr_resolve.emplace_back(pcontext.code.size(), node.target); + builder.adr(0, 0); + store(it, 0); + } + + void apply(ir::node_ref it, ir::extern_symbol const & node, types::type_ptr const &) + { + builder.ldr_pc(0, (lcontext.extern_symbols[node.name] - (std::int32_t)pcontext.code.size()) / 4); + store(it, 0); + } + + void apply(ir::node_ref, ir::assignment const & node, types::type_ptr const & type) + { + // TODO: array/array element assignment? + std::size_t src_offset = stack_size - stack_position.at(node.rhs); + + auto dst_type = node.lhs->inferred_type; + std::size_t dst_offset = stack_size - stack_position.at(node.lhs); + for (auto field_id : node.path) + { + if (auto struct_type = std::get_if(dst_type.get())) + { + auto struct_node = struct_type->node; + dst_type = struct_node->fields[field_id].inferred_type; + dst_offset += struct_node->fields[field_id].layout.offset; + } + else if (auto array_type = std::get_if(dst_type.get())) + { + dst_type = array_type->element_type; + dst_offset += field_id * ast::type_size(*array_type->element_type); + } + else + throw std::runtime_error("Unknown object type for field assignment"); + } + + copy_memory(31, src_offset, 31, dst_offset, ast::type_size(*dst_type), 0); + } + + void apply(ir::node_ref, ir::jump const & node, types::type_ptr const &) + { + lcontext.branch_resolve.emplace_back(pcontext.code.size(), node.target); + builder.b(0); + } + + void apply(ir::node_ref, ir::jump_if_zero const & node, types::type_ptr const &) + { + load(node.condition, 0); + lcontext.cbranch_resolve.emplace_back(pcontext.code.size(), node.target); + builder.cbz(0, 0); + } + + void apply(ir::node_ref, ir::jump_if_nonzero const & node, types::type_ptr const &) + { + load(node.condition, 0); + lcontext.cbranch_resolve.emplace_back(pcontext.code.size(), node.target); + builder.cbnz(0, 0); + } + + template + void apply_call(ir::node_ref it, Node const & node, types::type_ptr const & type, DoCall && do_call) + { + // TODO: handle the case when there weren't enough registers + std::uint8_t reg = 0; + std::uint8_t fp_reg = 0; + for (auto const & argument : node.arguments) + { + auto struct_type = std::get_if(argument->inferred_type.get()); + auto array_type = std::get_if(argument->inferred_type.get()); + if (struct_type || array_type) + { + // NB: fixed-size arrays are handled in the same way + // as structs of N identical fields + auto size = ast::type_size(*argument->inferred_type); + + if (auto hfa = get_hfa_data(lcontext, argument->inferred_type); hfa && hfa->count <= 4) + { + // HFA - passed in consecutive FP registers + std::int32_t base_offset = stack_size - stack_position.at(argument); + auto fp_mode = fp_mode_for(*hfa->element_type); + auto size = fp_size(fp_mode); + for (std::size_t i = 0; i < hfa->count; ++i) + builder.ldr_fp(fp_reg++, fp_mode, 31, (base_offset + i * size) / size); + } + else if (size <= 16) + { + // Small struct - passed in up to 2 GP registers + std::int32_t base_offset = stack_size - stack_position.at(argument); + std::int32_t offset = 0; + while (offset < size) + { + builder.ldr(reg++, 31, (base_offset + offset) / 8); + offset += 8; + } + } + else + { + // Large struct - passed by pointer + std::int32_t base_offset = stack_size - stack_position.at(argument); + builder.add_imm(31, reg++, base_offset); + } + } + else if (types::is_integer_like_type(*argument->inferred_type)) + load(argument, reg++); + else if (types::is_floating_point_type(*argument->inferred_type)) + load_fp(argument, fp_reg++, fp_mode_for(*argument->inferred_type)); + else + throw std::runtime_error("Unsupported function argument type"); + } + if (return_value_is_large_struct) + { + builder.sub_imm(31, 31, 16); + builder.str(8, 31, 0); + } + if (!lcontext.use_frame_pointer) + { + builder.sub_imm(31, 31, 16); + builder.str(30, 31, 0); + } + do_call(); + if (!lcontext.use_frame_pointer) + { + builder.ldr(30, 31, 0); + builder.add_imm(31, 31, 16); + } + if (return_value_is_large_struct) + { + builder.ldr(8, 31, 0); + builder.add_imm(31, 31, 16); + } + + // TODO: array return value? + auto size = ast::type_size(*type); + auto struct_type = std::get_if(type.get()); + auto array_type = std::get_if(type.get()); + if (size == 0) + {} + else if (struct_type || array_type) + { + // NB: fixed-size arrays are handled in the same way + // as structs of N identical fields + auto base_offset = stack_size - stack_position.at(it); + if (auto hfa = get_hfa_data(lcontext, type); hfa && hfa->count <= 4) + { + auto fp_mode = fp_mode_for(*hfa->element_type); + auto size = fp_size(fp_mode); + // HFA - returned in consecutive FP registers + for (std::size_t i = 0; i < hfa->count; ++i) + builder.str_fp(i, fp_mode, 31, (base_offset + i * size) / size); + } + else if (size <= 16) + { + // Small struct - returned in x0-x1 registers + builder.str(0, 31, base_offset / 8); + if (size > 8) + builder.str(1, 31, (base_offset + 8) / 8); + } + else + { + // Large struct - returned by pointer in x8 register + copy_memory(8, 0, 31, base_offset, size, 0); + } + } + else if (types::is_integer_like_type(*type)) + store(it, 0); + else if (types::is_floating_point_type(*type)) + store_fp(it, 0, fp_mode_for(*type)); + else + throw std::runtime_error("Unsupported return value type"); + } + + void apply(ir::node_ref it, ir::call const & node, types::type_ptr const & type) + { + apply_call(it, node, type, [&]{ + lcontext.branch_resolve.emplace_back(pcontext.code.size(), node.target); + builder.bl(0); + }); + } + + void apply(ir::node_ref it, ir::call_pointer const & node, types::type_ptr const & type) + { + apply_call(it, node, type, [&]{ + load(node.pointer, 9); + builder.bl_reg(9); + }); + } + + void apply(ir::node_ref, ir::return_value const & node, types::type_ptr const &) + { + // TODO: array return value? + if (node.value) + { + auto type = (*node.value)->inferred_type; + auto size = ast::type_size(*type); + auto struct_type = std::get_if(type.get()); + auto array_type = std::get_if(type.get()); + if (size == 0) + {} + else if (struct_type || array_type) + { + auto base_offset = stack_size - stack_position.at(*node.value); + if (auto hfa = get_hfa_data(lcontext, type); hfa && hfa->count <= 4) + { + auto fp_mode = fp_mode_for(*hfa->element_type); + auto size = fp_size(fp_mode); + // HFA - returned in consecutive FP registers + for (std::size_t i = 0; i < hfa->count; ++i) + builder.ldr_fp(i, fp_mode, 31, (base_offset + i * size) / size); + } + else if (size <= 16) + { + // Small struct - returned in x0-x1 registers + builder.ldr(0, 31, base_offset / 8); + if (size > 8) + builder.ldr(1, 31, (base_offset + 8) / 8); + } + else + { + // Large struct - returned by pointer in x8 register + copy_memory(31, base_offset, 8, 0, size, 0); + } + } + else if (types::is_integer_like_type(*type)) + load(*node.value, 0); + else if (types::is_floating_point_type(*type)) + load_fp(*node.value, 0, fp_mode_for(*type)); + else + throw std::runtime_error("Unsupported return value type"); + } + if (lcontext.use_frame_pointer) + { + builder.ldr(29, 31, (stack_size - 16) / 8); + builder.ldr(30, 31, (stack_size - 8) / 8); + } + if (stack_size > 0) + builder.add_imm(31, 31, stack_size); builder.ret(); } + void compile(ast::function_definition const * function_definition, ir::node_ref begin, ir::node_ref end) + { + auto result_type = function_definition->inferred_result_type; + auto struct_type = std::get_if(result_type.get()); + auto array_type = std::get_if(result_type.get()); + if (struct_type || array_type) + if (!get_hfa_data(lcontext, result_type) && ast::type_size(*result_type) > 16) + return_value_is_large_struct = true; + + stack_size = 0; + + if (lcontext.use_frame_pointer) + stack_size += 16; + + for (auto const & argument : function_definition->arguments) + { + auto size = ast::type_size(*argument.inferred_type); + stack_size += ((size + 7) / 8) * 8; + argument_position.push_back(stack_size); + } + + for (auto it = begin; it != end; ++it) + { + if (auto argument = std::get_if(&it->instruction)) + { + stack_position[it] = argument_position[argument->index]; + } + else if (ir::is_value_instruction(it->instruction)) + { + auto size = ast::type_size(*it->inferred_type); + if (size == 0) continue; + // TODO: inefficient for small types, maybe only round up to type alignment? + // Need to make sure all read/write arm64 instructions used can handle offsets that + // are not a multiple of 8 + stack_size += ((size + 7) / 8) * 8; + stack_position[it] = stack_size; + } + } + + stack_size = ((stack_size + 15) / 16) * 16; + + if (!std::holds_alternative(begin->instruction)) + throw std::runtime_error("First IR node of a function must be a label"); + + auto it = begin; + + lcontext.nodes[it] = pcontext.code.size(); + if (stack_size > 0) + builder.sub_imm(31, 31, stack_size); + if (lcontext.use_frame_pointer) + { + builder.str(29, 31, (stack_size - 16) / 8); + builder.str(30, 31, (stack_size - 8) / 8); + builder.add_imm(31, 29, stack_size - 16); + } + + // TODO: handle the case when there weren't enough registers + std::uint8_t reg = 0; + std::uint8_t fp_reg = 0; + for (std::size_t i = 0; i < function_definition->arguments.size(); ++i) + { + auto const & argument = function_definition->arguments[i]; + auto size = ast::type_size(*argument.inferred_type); + auto struct_type = std::get_if(argument.inferred_type.get()); + auto array_type = std::get_if(argument.inferred_type.get()); + if (size == 0) continue; + if (struct_type || array_type) + { + if (auto hfa = get_hfa_data(lcontext, argument.inferred_type); hfa && hfa->count <= 4) + { + // HFA - passed in consecutive FP registers + std::int32_t base_offset = stack_size - argument_position[i]; + auto fp_mode = fp_mode_for(*hfa->element_type); + auto size = fp_size(fp_mode); + for (std::size_t i = 0; i < hfa->count; ++i) + builder.str_fp(fp_reg++, fp_mode, 31, (base_offset + i * size) / size); + } + else if (size <= 16) + { + // Small struct - passed in up to 2 GP registers + std::int32_t base_offset = stack_size - argument_position[i]; + std::int32_t offset = 0; + while (offset < size) + { + builder.str(reg++, 31, (base_offset + offset) / 8); + offset += 8; + } + } + else + { + // Large struct - passed by pointer + std::int32_t dst_offset = stack_size - argument_position[i]; + copy_memory(reg++, 0, 31, dst_offset, size, 9); + } + } + else if (types::is_integer_like_type(*argument.inferred_type)) + builder.str(reg++, 31, (stack_size - argument_position[i]) / 8); + else if (types::is_floating_point_type(*argument.inferred_type)) + { + auto fp_mode = fp_mode_for(*argument.inferred_type); + builder.str_fp(fp_reg++, fp_mode, 31, (stack_size - argument_position[i]) / fp_size(fp_mode)); + } + else + throw std::runtime_error("Unknown argument type"); + } + ++it; + + for (; it != end; ++it) + { + // Uncomment to debug per-node instruction generation: + builder.nop(); + lcontext.nodes[it] = pcontext.code.size(); + std::visit([&](auto const & instruction){ apply(it, instruction, it->inferred_type); }, it->instruction); + } + } + private: - void push(std::uint8_t reg) + void load(ir::node_ref it, std::uint8_t reg) { - builder.sub_imm(31, 31, 16); - builder.str(reg, 31, 0); - stack_offset += 16; - scopes.back().stack_offset += 16; + std::int32_t offset = stack_size - stack_position.at(it); + builder.ldr(reg, 31, offset / 8); } - void pop(std::uint8_t reg) + void load_fp(ir::node_ref it, std::uint8_t reg, std::uint8_t mode) { - builder.ldr(reg, 31, 0); - builder.add_imm(31, 31, 16); - stack_offset -= 16; - scopes.back().stack_offset -= 16; + std::int32_t offset = stack_size - stack_position.at(it); + builder.ldr_fp(reg, mode, 31, offset / fp_size(mode)); } - void push_fp(std::uint8_t reg, std::uint8_t mode) + void store(ir::node_ref it, std::uint8_t reg) { - builder.sub_imm(31, 31, 16); - builder.str_fp(0, mode, 31, 0); - stack_offset += 16; - scopes.back().stack_offset += 16; + std::int32_t offset = stack_size - stack_position.at(it); + builder.str(reg, 31, offset / 8); } - void pop_fp(std::uint8_t reg, std::uint8_t mode) + void store_fp(ir::node_ref it, std::uint8_t reg, std::uint8_t mode) { - builder.ldr_fp(reg, mode, 31, 0); - builder.add_imm(31, 31, 16); - stack_offset -= 16; - scopes.back().stack_offset -= 16; - } - - // Set register @reg to -1 (all bits = 1) - void set_m1(std::uint8_t reg) - { - builder.or_not_reg(31, 31, reg); + std::int32_t offset = stack_size - stack_position.at(it); + builder.str_fp(reg, mode, 31, offset / fp_size(mode)); } // Sign- or zero-extend the register depending on the exact type @@ -1282,144 +1060,80 @@ namespace pslang::jit::aarch64 reg_extend_visitor{{}, builder, reg}.apply(*type); } - void scope_cleanup() + void copy_memory(std::uint8_t reg_src_addr, std::size_t src_offset, std::uint8_t reg_dst_addr, std::size_t dst_offset, std::size_t size, std::uint8_t tmp_reg) { - if (scopes.back().stack_offset > 0) - { - builder.add_imm(31, 31, scopes.back().stack_offset); - stack_offset -= scopes.back().stack_offset; - } - } + std::int32_t offset = 0; - // Returns offset from function entry stack frame - std::size_t lvalue_offset(ast::expression_ptr const & node) - { - if (auto identifier = std::get_if(node.get())) + while (size > 0) { - if (identifier->variable_node) - for (auto it = scopes.rbegin(); it != scopes.rend(); ++it) - if (auto jt = it->variables.find(identifier->variable_node); jt != it->variables.end()) - return jt->second.frame_offset; - throw std::runtime_error("Non-lvalue identifier: \"" + identifier->name + "\""); - } - else if (auto field_access = std::get_if(node.get())) - { - auto base_offset = lvalue_offset(field_access->object); - auto type = ast::get_type(*field_access->object); - if (auto struct_type = std::get_if(type.get())) + auto check_step = [&](std::size_t step) { - auto & struct_node = *struct_type->node; - std::optional field_id; - for (std::size_t i = 0; i < struct_node.fields.size(); ++i) - if (struct_node.fields[i].name == field_access->field_name) - { - field_id = i; - break; - } + return size >= step && (((src_offset + offset) % step) == 0) && (((dst_offset + offset) % step) == 0); + }; - if (!field_id) - throw std::runtime_error("Invalid field \"" + field_access->field_name + "\""); - - return base_offset - struct_node.fields[*field_id].layout.offset; + if (check_step(8)) + { + builder.ldr(tmp_reg, reg_src_addr, (src_offset + offset) / 8); + builder.str(tmp_reg, reg_dst_addr, (dst_offset + offset) / 8); + size -= 8; + offset += 8; + } + else if (check_step(4)) + { + builder.ldrw(tmp_reg, reg_src_addr, (src_offset + offset) / 4); + builder.strw(tmp_reg, reg_dst_addr, (dst_offset + offset) / 4); + size -= 4; + offset += 4; + } + else if (check_step(2)) + { + builder.ldrh(tmp_reg, reg_src_addr, (src_offset + offset) / 2); + builder.strh(tmp_reg, reg_dst_addr, (dst_offset + offset) / 2); + size -= 2; + offset += 2; } else - throw std::runtime_error("Invalid field access node"); + { + builder.ldrb(tmp_reg, reg_src_addr, src_offset + offset); + builder.strb(tmp_reg, reg_dst_addr, dst_offset + offset); + size -= 1; + offset += 1; + } } - else if (auto array_access = std::get_if(node.get())) - { - throw std::runtime_error("Not implemented"); - } - - throw std::runtime_error("Unknown lvalue node"); - } - }; - - // Main compilation visitor - struct compile_visitor - : ast::const_statement_visitor - { - program_context & pcontext; - local_context & lcontext; - instruction_builder & builder; - - 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_chain const & node) - { - for (auto const & block : node.blocks) - apply(*block.statements); - } - - void apply(ast::while_block const & node) - { - apply(*node.statements); - } - - void apply(ast::function_definition const & node) - { - compile_function_visitor{{}, {}, pcontext, lcontext}.do_apply(node); - - apply(*node.statements); - } - - void apply(ast::foreign_function_declaration const &) {} - - void apply(ast::return_statement const &) {} - - void apply(ast::struct_definition const &) {} - - void apply(ast::statement_list const & node) - { - lcontext.scopes.emplace_back(); - populate_symbols_visitor{{}, lcontext}.apply(node); - for (auto const & statement : node.statements) - apply(*statement); - // Don't pop_back entry point scope - if (lcontext.scopes.size() > 1) - lcontext.scopes.pop_back(); } }; } - void compile(program_context & pcontext, ast::statement_list_ptr const & statements) + void compile(program_context & pcontext, ir::module_context const & mcontext) { - // Add a fake AST node for the entry point - auto root = std::make_shared(ast::function_definition{ - { - {}, - {}, - std::make_shared(types::unit_type{}), - {}, - }, - statements, - {}, - }); - local_context lcontext; instruction_builder builder{pcontext.code}; - populate_constants_visitor{{}, {}, pcontext, lcontext}.apply(*statements); + { + populate_const_data_visitor visitor{pcontext, lcontext}; + for (auto it = mcontext.nodes->begin(); it != mcontext.nodes->end(); ++it) + std::visit([&](auto const & instruction){ visitor.apply(instruction, it->inferred_type); }, it->instruction); + } - compile_visitor{{}, pcontext, lcontext, builder}.apply(*root); + for (auto const & symbol : mcontext.symbols) + { + pcontext.symbols[symbol.first] = pcontext.code.size(); + compile_visitor visitor{pcontext, mcontext, lcontext, builder}; + visitor.compile(symbol.first, symbol.second.begin, symbol.second.end); + } - for (auto const & resolve : lcontext.resolve) - builder.adr_inject(pcontext.code.data() + resolve.instruction_offset, lcontext.functions.at(resolve.node) - resolve.instruction_offset); + pcontext.entry_point = lcontext.nodes.at(mcontext.entry_point); - for (auto const & foreign : lcontext.foreign_address) - pcontext.foreign_resolve.push_back({foreign.first, foreign.second}); + for (auto const & resolve : lcontext.branch_resolve) + builder.b_inject(pcontext.code.data() + resolve.offset, (lcontext.nodes.at(resolve.target) - resolve.offset) / 4); - for (auto const & function : lcontext.scopes.front().functions) - pcontext.symbols[function.second] = lcontext.functions.at(function.second); + for (auto const & resolve : lcontext.cbranch_resolve) + builder.cb_inject(pcontext.code.data() + resolve.offset, (lcontext.nodes.at(resolve.target) - resolve.offset) / 4); - pcontext.entry_point = lcontext.functions.at(std::get_if(root.get())); + for (auto const & resolve : lcontext.adr_resolve) + builder.adr_inject(pcontext.code.data() + resolve.offset, (lcontext.nodes.at(resolve.target) - resolve.offset) / 4); } } diff --git a/libs/jit/source/arch/aarch64/compiler_v2.cpp b/libs/jit/source/arch/aarch64/compiler_v2.cpp deleted file mode 100644 index 41ab352..0000000 --- a/libs/jit/source/arch/aarch64/compiler_v2.cpp +++ /dev/null @@ -1,1139 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -namespace pslang::jit::aarch64 -{ - - namespace - { - - // Homogeneous floating-point aggregate: up to 4 floating-point members - // of the same type (after struct flattening) - struct hfa_data - { - types::type_ptr element_type; - std::size_t count; - }; - - struct local_context - { - bool use_frame_pointer = true; - - std::unordered_map> struct_hfa; - - std::unordered_map extern_symbols; - std::unordered_map nodes; - - std::unordered_map f16_constants; - std::unordered_map f32_constants; - std::unordered_map f64_constants; - - struct resolve_data - { - std::int32_t offset; - ir::node_ref target; - }; - - std::vector branch_resolve; - std::vector cbranch_resolve; - std::vector adr_resolve; - }; - - std::uint8_t fp_mode_for(types::type const & type) - { - if (types::equal(type, types::primitive_type(types::f16_type{}))) - return 1; - if (types::equal(type, types::primitive_type(types::f32_type{}))) - return 2; - return 3; - } - - std::int32_t fp_size(std::uint8_t mode) - { - return 1 << mode; - } - - std::optional get_hfa_data(local_context & lcontext, types::type_ptr const & type); - - std::optional compute_hfa_data(local_context & lcontext, ast::struct_definition const * node) - { - types::type_ptr type = nullptr; - std::size_t count = 0; - - for (std::size_t i = 0; i < node->fields.size(); ++i) - { - auto const & field = node->fields[i]; - - // NB: recursion must be impossible due to prior checks in type checker - if (auto subdata = get_hfa_data(lcontext, field.inferred_type)) - { - if (type && !types::equal(*type, *subdata->element_type)) - return std::nullopt; - - type = subdata->element_type; - count += subdata->count; - } - else - return std::nullopt; - } - - if (count == 0) - return std::nullopt; - - return hfa_data{type, count}; - } - - std::optional get_hfa_data(local_context & lcontext, types::type_ptr const & type) - { - if (auto struct_type = std::get_if(type.get())) - { - if (auto it = lcontext.struct_hfa.find(struct_type->node); it != lcontext.struct_hfa.end()) - return it->second; - - auto result = compute_hfa_data(lcontext, struct_type->node); - lcontext.struct_hfa[struct_type->node] = result; - return result; - } - else if (auto array_type = std::get_if(type.get())) - { - if (auto subdata = get_hfa_data(lcontext, array_type->element_type)) - return hfa_data{subdata->element_type, subdata->count * array_type->size}; - else - return std::nullopt; - } - else if (types::is_floating_point_type(*type)) - return hfa_data{type, 1}; - else - return std::nullopt; - } - - struct populate_const_data_visitor - { - program_context & pcontext; - local_context & lcontext; - - template - void apply(Node const & node, types::type_ptr const &) - {} - - void apply(ir::literal const & node, types::type_ptr const &) - { - if (auto f16_literal = std::get_if(&node.value)) - { - lcontext.f16_constants[f16_literal->value.repr] = pcontext.code.size(); - push_bytes(f16_literal->value.repr); - } - else if (auto f32_literal = std::get_if(&node.value)) - { - lcontext.f32_constants[f32_literal->value] = pcontext.code.size(); - push_bytes(f32_literal->value); - } - else if (auto f64_literal = std::get_if(&node.value)) - { - lcontext.f32_constants[f64_literal->value] = pcontext.code.size(); - push_bytes(f64_literal->value); - } - } - - void apply(ir::extern_symbol const & node, types::type_ptr const &) - { - std::int32_t offset = pcontext.code.size(); - lcontext.extern_symbols[node.name] = offset; - pcontext.foreign_resolve.push_back({node.name, offset}); - push_bytes(nullptr); - } - - private: - template - void push_bytes(T const & value) - { - auto begin = (std::uint8_t const *)(&value); - auto end = begin + sizeof(value); - pcontext.code.insert(pcontext.code.end(), begin, end); - } - }; - - // Set register @reg to -1 (all bits = 1) - void set_m1(instruction_builder & builder, std::uint8_t reg) - { - builder.or_not_reg(31, 31, reg); - } - - struct literal_visitor - { - program_context & pcontext; - local_context & lcontext; - instruction_builder & builder; - - void operator()(ast::bool_literal const & node) - { - if (node.value) - set_m1(builder, 0); - else - builder.movz(0, 0); - } - - template - requires(std::is_integral_v && !std::is_same_v) - void operator()(ast::primitive_literal_base const & node) - { - for (std::size_t i = 0; i < sizeof(T); i += 2) - { - if (i == 0) - { - builder.movz(0, std::uint64_t(node.value)); - } - else - { - auto val = std::uint16_t(std::uint64_t(node.value) >> (i * 8)); - if (val != 0) - builder.movk(0, val, i / 2); - } - } - - if (sizeof(T) < 8) - { - if constexpr (std::is_signed_v) - { - if (node.value < 0) - builder.sbfm(0, 0, sizeof(T) * 8); - } - } - } - - void operator()(ast::f16_literal const & node) - { - auto offset = lcontext.f16_constants.at(node.value.repr); - std::int32_t current = pcontext.code.size(); - builder.ldr_fp_pc(0, 0, (offset - current) / 4); - builder.fcvt(0, 0b10, 0, 0b01); - } - - void operator()(ast::f32_literal const & node) - { - auto offset = lcontext.f32_constants.at(node.value); - std::int32_t current = pcontext.code.size(); - builder.ldr_fp_pc(0, 0, (offset - current) / 4); - } - - void operator()(ast::f64_literal const & node) - { - auto offset = lcontext.f64_constants.at(node.value); - std::int32_t current = pcontext.code.size(); - builder.ldr_fp_pc(0, 1, (offset - current) / 4); - } - }; - - struct reg_extend_visitor - : types::const_visitor - { - using const_visitor::apply; - - instruction_builder & builder; - std::uint8_t reg; - - void apply(types::bool_type const &) - { - builder.ubfm(reg, reg, 8); - } - - void apply(types::f16_type const &) - {} - - void apply(types::f32_type const &) - {} - - void apply(types::f64_type const &) - {} - - template - void apply(types::primitive_type_base const &) - { - if constexpr (sizeof(T) == 8) - { - return; - } - - if constexpr (std::is_signed_v) - { - builder.sbfm(reg, reg, sizeof(T) * 8); - } - - if constexpr (std::is_unsigned_v) - { - builder.ubfm(reg, reg, sizeof(T) * 8); - } - } - - template - void apply(T const &) - { - throw std::runtime_error(std::string("reg_extend_visitor is not implemented for ") + typeid(T).name()); - } - }; - - struct compile_visitor - { - program_context & pcontext; - ir::module_context const & mcontext; - local_context & lcontext; - instruction_builder & builder; - - std::vector argument_position; - std::unordered_map stack_position; - std::int32_t stack_size = 0; - bool return_value_is_large_struct = false; - - void apply(ir::node_ref, ir::label const &, types::type_ptr const &) - {} - - void apply(ir::node_ref it, ir::literal const & node, types::type_ptr const & type) - { - std::visit(literal_visitor{pcontext, lcontext, builder}, node.value); - if (types::is_integer_like_type(*type)) - store(it, 0); - else if (types::is_floating_point_type(*type)) - store_fp(it, 0, fp_mode_for(*type)); - } - - void apply(ir::node_ref it, ir::alloc const & node, types::type_ptr const &) - { - // Nothing to do: alloc just allocates a node of a struct type, - // but we already allocated stack space for it - } - - void apply(ir::node_ref it, ir::copy const & node, types::type_ptr const & type) - { - // TODO: array/array element copy? - auto size = ast::type_size(*type); - auto dst_offset = stack_size - stack_position.at(it); - - auto src_type = node.source->inferred_type; - auto src_offset = stack_size - stack_position.at(node.source); - for (auto field_id : node.path) - { - auto const & field = std::get(*src_type).node->fields[field_id]; - src_type = field.inferred_type; - src_offset += field.layout.offset; - } - - copy_memory(31, src_offset, 31, dst_offset, size, 0); - } - - void apply(ir::node_ref it, ir::load const & node, types::type_ptr const & type) - { - // TODO: array/array element load? - load(node.ptr, 0); - auto size = ast::type_size(*type); - auto dst_offset = stack_size - stack_position.at(it); - copy_memory(0, 0, 31, dst_offset, size, 1); - } - - void apply(ir::node_ref, ir::store const & node, types::type_ptr const & type) - { - // TODO: array/array element store? - load(node.ptr, 0); - auto size = ast::type_size(*type); - std::int32_t src_offset = stack_size - stack_position.at(node.value); - copy_memory(31, src_offset, 0, 0, size, 1); - } - - void apply(ir::node_ref it, ir::unary_operation const & node, types::type_ptr const & type) - { - switch (node.type) - { - case ast::unary_operation_type::negation: - if (types::is_integer_type(*type)) - { - load(node.arg1, 0); - builder.sub_reg(31, 0, 0); - store(it, 0); - } - else if (types::is_floating_point_type(*type)) - { - auto mode = fp_mode_for(*type); - load_fp(node.arg1, 0, mode); - builder.fneg(0, mode, 0); - store_fp(it, 0, mode); - } - break; - case ast::unary_operation_type::logical_not: - load(node.arg1, 0); - builder.or_not_reg(31, 0, 0); - store(it, 0); - break; - case ast::unary_operation_type::address_of: - case ast::unary_operation_type::mutable_address_of: - builder.add_imm(31, 0, stack_size - stack_position.at(node.arg1)); - store(it, 0); - break; - case ast::unary_operation_type::dereference: - throw std::runtime_error("Dereference operator mush not be present in compiled IR"); - } - } - - void apply(ir::node_ref it, ir::binary_operation const & node, types::type_ptr const & type) - { - auto arg1_type = node.arg1->inferred_type; - bool const is_fp = types::is_floating_point_type(*arg1_type); - bool const result_is_fp = types::is_floating_point_type(*type); - std::uint8_t const fp_mode = fp_mode_for(*arg1_type); - - if (is_fp) - { - load_fp(node.arg1, 0, fp_mode); - load_fp(node.arg2, 1, fp_mode); - } - else - { - load(node.arg1, 0); - load(node.arg2, 1); - } - - switch (node.type) - { - case ast::binary_operation_type::addition: - if (is_fp) - builder.fadd(0, 1, fp_mode, 0); - else - { - builder.add_reg(0, 1, 0); - } - break; - case ast::binary_operation_type::subtraction: - if (is_fp) - builder.fsub(0, 1, fp_mode, 0); - else - { - builder.sub_reg(0, 1, 0); - } - break; - case ast::binary_operation_type::multiplication: - if (is_fp) - builder.fmul(0, 1, fp_mode, 0); - else - { - builder.mul_reg(0, 1, 0); - } - break; - case ast::binary_operation_type::division: - if (is_fp) - builder.fdiv(0, 1, fp_mode, 0); - else - { - extend(0, type); - extend(1, type); - if (types::is_signed_integer_type(*type)) - builder.sdiv_reg(0, 1, 0); - else - builder.udiv_reg(0, 1, 0); - } - break; - case ast::binary_operation_type::remainder: - extend(0, type); - extend(1, type); - if (types::is_signed_integer_type(*type)) - { - builder.sdiv_reg(0, 1, 2); - builder.mul_reg(1, 2, 1); - builder.sub_reg(0, 1, 0); - } - else if (types::is_unsigned_integer_type(*type)) - { - builder.udiv_reg(0, 1, 2); - builder.mul_reg(1, 2, 1); - builder.sub_reg(0, 1, 0); - } - break; - case ast::binary_operation_type::binary_and: - builder.and_reg(0, 1, 0); - break; - case ast::binary_operation_type::logical_and: - throw std::runtime_error("Short-circuiting operators must have been unwrapped in IR compiler"); - case ast::binary_operation_type::binary_or: - builder.or_reg(0, 1, 0); - break; - case ast::binary_operation_type::logical_or: - throw std::runtime_error("Short-circuiting operators must have been unwrapped in IR compiler"); - case ast::binary_operation_type::logical_xor: - builder.xor_reg(0, 1, 0); - break; - case ast::binary_operation_type::equals: - if (is_fp) - { - builder.fcmp(0, 1, fp_mode); - builder.csetm(0, 0b0000); - } - else - { - extend(0, node.arg1->inferred_type); - extend(1, node.arg2->inferred_type); - builder.cmp_reg(0, 1); - builder.csetm(0, 0b0000); - } - break; - case ast::binary_operation_type::not_equals: - if (is_fp) - { - builder.fcmp(0, 1, fp_mode); - builder.csetm(0, 0b0001); - } - else - { - extend(0, node.arg1->inferred_type); - extend(1, node.arg2->inferred_type); - builder.cmp_reg(0, 1); - builder.csetm(0, 0b0001); - } - break; - case ast::binary_operation_type::less: - if (is_fp) - { - builder.fcmp(0, 1, fp_mode); - builder.csetm(0, 0b0100); - } - else - { - extend(0, node.arg1->inferred_type); - extend(1, node.arg2->inferred_type); - builder.cmp_reg(1, 0); - if (types::is_bool_type(*node.arg1->inferred_type) || types::is_unsigned_integer_type(*node.arg1->inferred_type)) - builder.csetm(0, 0b1000); - else - builder.csetm(0, 0b1100); - } - break; - case ast::binary_operation_type::greater: - if (is_fp) - { - builder.fcmp(1, 0, fp_mode); - builder.csetm(0, 0b0100); - } - else - { - extend(0, node.arg1->inferred_type); - extend(1, node.arg2->inferred_type); - builder.cmp_reg(0, 1); - if (types::is_bool_type(*node.arg1->inferred_type) || types::is_unsigned_integer_type(*node.arg1->inferred_type)) - builder.csetm(0, 0b1000); - else - builder.csetm(0, 0b1100); - } - break; - case ast::binary_operation_type::less_equals: - if (is_fp) - { - builder.fcmp(0, 1, fp_mode); - builder.csetm(0, 0b1001); - } - else - { - extend(0, node.arg1->inferred_type); - extend(1, node.arg2->inferred_type); - builder.cmp_reg(0, 1); - if (types::is_bool_type(*node.arg1->inferred_type) || types::is_unsigned_integer_type(*node.arg1->inferred_type)) - builder.csetm(0, 0b1001); - else - builder.csetm(0, 0b1101); - } - break; - case ast::binary_operation_type::greater_equals: - if (is_fp) - { - builder.fcmp(1, 0, fp_mode); - builder.csetm(0, 0b1001); - } - else - { - extend(0, node.arg1->inferred_type); - extend(1, node.arg2->inferred_type); - builder.cmp_reg(1, 0); - if (types::is_bool_type(*node.arg1->inferred_type) || types::is_unsigned_integer_type(*node.arg1->inferred_type)) - builder.csetm(0, 0b1001); - else - builder.csetm(0, 0b1101); - } - break; - default: - { - std::ostringstream os; - os << "binary operation " << node.type << " is not implemented"; - throw std::runtime_error(os.str()); - } - } - - if (result_is_fp) - store_fp(it, 0, fp_mode); - else - store(it, 0); - } - - void apply(ir::node_ref it, ir::cast_operation const & node, types::type_ptr const &) - { - auto src_type = node.arg1->inferred_type; - auto dst_type = node.target_type; - - if (types::equal(*src_type, *dst_type) || (types::is_pointer_type(*src_type) && types::is_pointer_type(*dst_type))) - { - load(node.arg1, 0); - store(it, 0); - return; - } - - if (auto array_type = std::get_if(src_type.get())) - { - if (auto pointer_type = std::get_if(dst_type.get())) - { - if (types::equal(*array_type->element_type, *pointer_type->referenced_type)) - { - std::int32_t offset = stack_size - stack_position.at(node.arg1); - builder.add_imm(31, 0, offset); - store(it, 0); - return; - } - } - } - - if (types::is_numeric_type(*src_type) && types::is_numeric_type(*dst_type)) - { - if (types::is_integer_type(*src_type)) - { - load(node.arg1, 0); - if (types::is_integer_type(*dst_type)) - { - extend(0, dst_type); - } - else if (types::is_floating_point_type(*dst_type)) - { - auto dst_mode = fp_mode_for(*dst_type); - if (types::is_signed_integer_type(*src_type)) - { - builder.fmov(0, 0, 3, 1); - builder.scvtf(0, 0, 3); - if (dst_mode != 3) - builder.fcvt(0, 3, 0, dst_mode); - } - else if (types::is_unsigned_integer_type(*src_type)) - { - builder.fmov(0, 0, 3, 1); - builder.ucvtf(0, 0, 3); - if (dst_mode != 3) - builder.fcvt(0, 3, 0, dst_mode); - } - } - } - else if (types::is_floating_point_type(*src_type)) - { - auto src_mode = fp_mode_for(*src_type); - if (types::is_integer_type(*dst_type)) - { - if (types::is_signed_integer_type(*dst_type)) - { - builder.fcvtns(0, 0, src_mode); - extend(0, dst_type); - } - else if (types::is_unsigned_integer_type(*dst_type)) - { - builder.fcvtnu(0, 0, src_mode); - extend(0, dst_type); - } - } - else if (types::is_floating_point_type(*dst_type)) - { - auto dst_mode = fp_mode_for(*dst_type); - builder.fcvt(0, src_mode, 0, dst_mode); - } - } - - if (types::is_integer_type(*dst_type)) - { - store(it, 0); - } - else if (types::is_floating_point_type(*dst_type)) - { - store_fp(it, 0, fp_mode_for(*dst_type)); - } - - return; - } - - throw std::runtime_error("Unknown types for cast instruction"); - } - - void apply(ir::node_ref it, ir::argument const & node, types::type_ptr const & type) - { - // Nothing to do: arguments already pushed on stack in function preamble - } - - void apply(ir::node_ref it, ir::instruction_address const & node, types::type_ptr const &) - { - lcontext.adr_resolve.emplace_back(pcontext.code.size(), node.target); - builder.adr(0, 0); - store(it, 0); - } - - void apply(ir::node_ref it, ir::extern_symbol const & node, types::type_ptr const &) - { - builder.ldr_pc(0, (lcontext.extern_symbols[node.name] - (std::int32_t)pcontext.code.size()) / 4); - store(it, 0); - } - - void apply(ir::node_ref, ir::assignment const & node, types::type_ptr const & type) - { - // TODO: array/array element assignment? - std::size_t src_offset = stack_size - stack_position.at(node.rhs); - - auto dst_type = node.lhs->inferred_type; - std::size_t dst_offset = stack_size - stack_position.at(node.lhs); - for (auto field_id : node.path) - { - if (auto struct_type = std::get_if(dst_type.get())) - { - auto struct_node = struct_type->node; - dst_type = struct_node->fields[field_id].inferred_type; - dst_offset += struct_node->fields[field_id].layout.offset; - } - else if (auto array_type = std::get_if(dst_type.get())) - { - dst_type = array_type->element_type; - dst_offset += field_id * ast::type_size(*array_type->element_type); - } - else - throw std::runtime_error("Unknown object type for field assignment"); - } - - copy_memory(31, src_offset, 31, dst_offset, ast::type_size(*dst_type), 0); - } - - void apply(ir::node_ref, ir::jump const & node, types::type_ptr const &) - { - lcontext.branch_resolve.emplace_back(pcontext.code.size(), node.target); - builder.b(0); - } - - void apply(ir::node_ref, ir::jump_if_zero const & node, types::type_ptr const &) - { - load(node.condition, 0); - lcontext.cbranch_resolve.emplace_back(pcontext.code.size(), node.target); - builder.cbz(0, 0); - } - - void apply(ir::node_ref, ir::jump_if_nonzero const & node, types::type_ptr const &) - { - load(node.condition, 0); - lcontext.cbranch_resolve.emplace_back(pcontext.code.size(), node.target); - builder.cbnz(0, 0); - } - - template - void apply_call(ir::node_ref it, Node const & node, types::type_ptr const & type, DoCall && do_call) - { - // TODO: handle the case when there weren't enough registers - std::uint8_t reg = 0; - std::uint8_t fp_reg = 0; - for (auto const & argument : node.arguments) - { - auto struct_type = std::get_if(argument->inferred_type.get()); - auto array_type = std::get_if(argument->inferred_type.get()); - if (struct_type || array_type) - { - // NB: fixed-size arrays are handled in the same way - // as structs of N identical fields - auto size = ast::type_size(*argument->inferred_type); - - if (auto hfa = get_hfa_data(lcontext, argument->inferred_type); hfa && hfa->count <= 4) - { - // HFA - passed in consecutive FP registers - std::int32_t base_offset = stack_size - stack_position.at(argument); - auto fp_mode = fp_mode_for(*hfa->element_type); - auto size = fp_size(fp_mode); - for (std::size_t i = 0; i < hfa->count; ++i) - builder.ldr_fp(fp_reg++, fp_mode, 31, (base_offset + i * size) / size); - } - else if (size <= 16) - { - // Small struct - passed in up to 2 GP registers - std::int32_t base_offset = stack_size - stack_position.at(argument); - std::int32_t offset = 0; - while (offset < size) - { - builder.ldr(reg++, 31, (base_offset + offset) / 8); - offset += 8; - } - } - else - { - // Large struct - passed by pointer - std::int32_t base_offset = stack_size - stack_position.at(argument); - builder.add_imm(31, reg++, base_offset); - } - } - else if (types::is_integer_like_type(*argument->inferred_type)) - load(argument, reg++); - else if (types::is_floating_point_type(*argument->inferred_type)) - load_fp(argument, fp_reg++, fp_mode_for(*argument->inferred_type)); - else - throw std::runtime_error("Unsupported function argument type"); - } - if (return_value_is_large_struct) - { - builder.sub_imm(31, 31, 16); - builder.str(8, 31, 0); - } - if (!lcontext.use_frame_pointer) - { - builder.sub_imm(31, 31, 16); - builder.str(30, 31, 0); - } - do_call(); - if (!lcontext.use_frame_pointer) - { - builder.ldr(30, 31, 0); - builder.add_imm(31, 31, 16); - } - if (return_value_is_large_struct) - { - builder.ldr(8, 31, 0); - builder.add_imm(31, 31, 16); - } - - // TODO: array return value? - auto size = ast::type_size(*type); - auto struct_type = std::get_if(type.get()); - auto array_type = std::get_if(type.get()); - if (size == 0) - {} - else if (struct_type || array_type) - { - // NB: fixed-size arrays are handled in the same way - // as structs of N identical fields - auto base_offset = stack_size - stack_position.at(it); - if (auto hfa = get_hfa_data(lcontext, type); hfa && hfa->count <= 4) - { - auto fp_mode = fp_mode_for(*hfa->element_type); - auto size = fp_size(fp_mode); - // HFA - returned in consecutive FP registers - for (std::size_t i = 0; i < hfa->count; ++i) - builder.str_fp(i, fp_mode, 31, (base_offset + i * size) / size); - } - else if (size <= 16) - { - // Small struct - returned in x0-x1 registers - builder.str(0, 31, base_offset / 8); - if (size > 8) - builder.str(1, 31, (base_offset + 8) / 8); - } - else - { - // Large struct - returned by pointer in x8 register - copy_memory(8, 0, 31, base_offset, size, 0); - } - } - else if (types::is_integer_like_type(*type)) - store(it, 0); - else if (types::is_floating_point_type(*type)) - store_fp(it, 0, fp_mode_for(*type)); - else - throw std::runtime_error("Unsupported return value type"); - } - - void apply(ir::node_ref it, ir::call const & node, types::type_ptr const & type) - { - apply_call(it, node, type, [&]{ - lcontext.branch_resolve.emplace_back(pcontext.code.size(), node.target); - builder.bl(0); - }); - } - - void apply(ir::node_ref it, ir::call_pointer const & node, types::type_ptr const & type) - { - apply_call(it, node, type, [&]{ - load(node.pointer, 9); - builder.bl_reg(9); - }); - } - - void apply(ir::node_ref, ir::return_value const & node, types::type_ptr const &) - { - // TODO: array return value? - if (node.value) - { - auto type = (*node.value)->inferred_type; - auto size = ast::type_size(*type); - auto struct_type = std::get_if(type.get()); - auto array_type = std::get_if(type.get()); - if (size == 0) - {} - else if (struct_type || array_type) - { - auto base_offset = stack_size - stack_position.at(*node.value); - if (auto hfa = get_hfa_data(lcontext, type); hfa && hfa->count <= 4) - { - auto fp_mode = fp_mode_for(*hfa->element_type); - auto size = fp_size(fp_mode); - // HFA - returned in consecutive FP registers - for (std::size_t i = 0; i < hfa->count; ++i) - builder.ldr_fp(i, fp_mode, 31, (base_offset + i * size) / size); - } - else if (size <= 16) - { - // Small struct - returned in x0-x1 registers - builder.ldr(0, 31, base_offset / 8); - if (size > 8) - builder.ldr(1, 31, (base_offset + 8) / 8); - } - else - { - // Large struct - returned by pointer in x8 register - copy_memory(31, base_offset, 8, 0, size, 0); - } - } - else if (types::is_integer_like_type(*type)) - load(*node.value, 0); - else if (types::is_floating_point_type(*type)) - load_fp(*node.value, 0, fp_mode_for(*type)); - else - throw std::runtime_error("Unsupported return value type"); - } - if (lcontext.use_frame_pointer) - { - builder.ldr(29, 31, (stack_size - 16) / 8); - builder.ldr(30, 31, (stack_size - 8) / 8); - } - if (stack_size > 0) - builder.add_imm(31, 31, stack_size); - builder.ret(); - } - - void compile(ast::function_definition const * function_definition, ir::node_ref begin, ir::node_ref end) - { - auto result_type = function_definition->inferred_result_type; - auto struct_type = std::get_if(result_type.get()); - auto array_type = std::get_if(result_type.get()); - if (struct_type || array_type) - if (!get_hfa_data(lcontext, result_type) && ast::type_size(*result_type) > 16) - return_value_is_large_struct = true; - - stack_size = 0; - - if (lcontext.use_frame_pointer) - stack_size += 16; - - for (auto const & argument : function_definition->arguments) - { - auto size = ast::type_size(*argument.inferred_type); - stack_size += ((size + 7) / 8) * 8; - argument_position.push_back(stack_size); - } - - for (auto it = begin; it != end; ++it) - { - if (auto argument = std::get_if(&it->instruction)) - { - stack_position[it] = argument_position[argument->index]; - } - else if (ir::is_value_instruction(it->instruction)) - { - auto size = ast::type_size(*it->inferred_type); - if (size == 0) continue; - // TODO: inefficient for small types, maybe only round up to type alignment? - // Need to make sure all read/write arm64 instructions used can handle offsets that - // are not a multiple of 8 - stack_size += ((size + 7) / 8) * 8; - stack_position[it] = stack_size; - } - } - - stack_size = ((stack_size + 15) / 16) * 16; - - if (!std::holds_alternative(begin->instruction)) - throw std::runtime_error("First IR node of a function must be a label"); - - auto it = begin; - - lcontext.nodes[it] = pcontext.code.size(); - if (stack_size > 0) - builder.sub_imm(31, 31, stack_size); - if (lcontext.use_frame_pointer) - { - builder.str(29, 31, (stack_size - 16) / 8); - builder.str(30, 31, (stack_size - 8) / 8); - builder.add_imm(31, 29, stack_size - 16); - } - - // TODO: handle the case when there weren't enough registers - std::uint8_t reg = 0; - std::uint8_t fp_reg = 0; - for (std::size_t i = 0; i < function_definition->arguments.size(); ++i) - { - auto const & argument = function_definition->arguments[i]; - auto size = ast::type_size(*argument.inferred_type); - auto struct_type = std::get_if(argument.inferred_type.get()); - auto array_type = std::get_if(argument.inferred_type.get()); - if (size == 0) continue; - if (struct_type || array_type) - { - if (auto hfa = get_hfa_data(lcontext, argument.inferred_type); hfa && hfa->count <= 4) - { - // HFA - passed in consecutive FP registers - std::int32_t base_offset = stack_size - argument_position[i]; - auto fp_mode = fp_mode_for(*hfa->element_type); - auto size = fp_size(fp_mode); - for (std::size_t i = 0; i < hfa->count; ++i) - builder.str_fp(fp_reg++, fp_mode, 31, (base_offset + i * size) / size); - } - else if (size <= 16) - { - // Small struct - passed in up to 2 GP registers - std::int32_t base_offset = stack_size - argument_position[i]; - std::int32_t offset = 0; - while (offset < size) - { - builder.str(reg++, 31, (base_offset + offset) / 8); - offset += 8; - } - } - else - { - // Large struct - passed by pointer - std::int32_t dst_offset = stack_size - argument_position[i]; - copy_memory(reg++, 0, 31, dst_offset, size, 9); - } - } - else if (types::is_integer_like_type(*argument.inferred_type)) - builder.str(reg++, 31, (stack_size - argument_position[i]) / 8); - else if (types::is_floating_point_type(*argument.inferred_type)) - { - auto fp_mode = fp_mode_for(*argument.inferred_type); - builder.str_fp(fp_reg++, fp_mode, 31, (stack_size - argument_position[i]) / fp_size(fp_mode)); - } - else - throw std::runtime_error("Unknown argument type"); - } - ++it; - - for (; it != end; ++it) - { - // Uncomment to debug per-node instruction generation: - builder.nop(); - lcontext.nodes[it] = pcontext.code.size(); - std::visit([&](auto const & instruction){ apply(it, instruction, it->inferred_type); }, it->instruction); - } - } - - private: - void load(ir::node_ref it, std::uint8_t reg) - { - std::int32_t offset = stack_size - stack_position.at(it); - builder.ldr(reg, 31, offset / 8); - } - - void load_fp(ir::node_ref it, std::uint8_t reg, std::uint8_t mode) - { - std::int32_t offset = stack_size - stack_position.at(it); - builder.ldr_fp(reg, mode, 31, offset / fp_size(mode)); - } - - void store(ir::node_ref it, std::uint8_t reg) - { - std::int32_t offset = stack_size - stack_position.at(it); - builder.str(reg, 31, offset / 8); - } - - void store_fp(ir::node_ref it, std::uint8_t reg, std::uint8_t mode) - { - std::int32_t offset = stack_size - stack_position.at(it); - builder.str_fp(reg, mode, 31, offset / fp_size(mode)); - } - - // Sign- or zero-extend the register depending on the exact type - void extend(std::uint8_t reg, types::type_ptr const & type) - { - reg_extend_visitor{{}, builder, reg}.apply(*type); - } - - void copy_memory(std::uint8_t reg_src_addr, std::size_t src_offset, std::uint8_t reg_dst_addr, std::size_t dst_offset, std::size_t size, std::uint8_t tmp_reg) - { - std::int32_t offset = 0; - - while (size > 0) - { - auto check_step = [&](std::size_t step) - { - return size >= step && (((src_offset + offset) % step) == 0) && (((dst_offset + offset) % step) == 0); - }; - - if (check_step(8)) - { - builder.ldr(tmp_reg, reg_src_addr, (src_offset + offset) / 8); - builder.str(tmp_reg, reg_dst_addr, (dst_offset + offset) / 8); - size -= 8; - offset += 8; - } - else if (check_step(4)) - { - builder.ldrw(tmp_reg, reg_src_addr, (src_offset + offset) / 4); - builder.strw(tmp_reg, reg_dst_addr, (dst_offset + offset) / 4); - size -= 4; - offset += 4; - } - else if (check_step(2)) - { - builder.ldrh(tmp_reg, reg_src_addr, (src_offset + offset) / 2); - builder.strh(tmp_reg, reg_dst_addr, (dst_offset + offset) / 2); - size -= 2; - offset += 2; - } - else - { - builder.ldrb(tmp_reg, reg_src_addr, src_offset + offset); - builder.strb(tmp_reg, reg_dst_addr, dst_offset + offset); - size -= 1; - offset += 1; - } - } - } - }; - - } - - void compile(program_context & pcontext, ir::module_context const & mcontext) - { - local_context lcontext; - - instruction_builder builder{pcontext.code}; - - { - populate_const_data_visitor visitor{pcontext, lcontext}; - for (auto it = mcontext.nodes->begin(); it != mcontext.nodes->end(); ++it) - std::visit([&](auto const & instruction){ visitor.apply(instruction, it->inferred_type); }, it->instruction); - } - - for (auto const & symbol : mcontext.symbols) - { - pcontext.symbols[symbol.first] = pcontext.code.size(); - compile_visitor visitor{pcontext, mcontext, lcontext, builder}; - visitor.compile(symbol.first, symbol.second.begin, symbol.second.end); - } - - pcontext.entry_point = lcontext.nodes.at(mcontext.entry_point); - - for (auto const & resolve : lcontext.branch_resolve) - builder.b_inject(pcontext.code.data() + resolve.offset, (lcontext.nodes.at(resolve.target) - resolve.offset) / 4); - - for (auto const & resolve : lcontext.cbranch_resolve) - builder.cb_inject(pcontext.code.data() + resolve.offset, (lcontext.nodes.at(resolve.target) - resolve.offset) / 4); - - for (auto const & resolve : lcontext.adr_resolve) - builder.adr_inject(pcontext.code.data() + resolve.offset, (lcontext.nodes.at(resolve.target) - resolve.offset) / 4); - } - -} diff --git a/libs/jit/source/jit.cpp b/libs/jit/source/jit.cpp index f520c39..4b9b99c 100644 --- a/libs/jit/source/jit.cpp +++ b/libs/jit/source/jit.cpp @@ -6,20 +6,6 @@ namespace pslang::jit { - void compile(program_context & context, ast::statement_list_ptr const & statements) - { - switch (context.abi) - { - case abi::itanium: - throw std::runtime_error("Itanium ABI JIT not implemented"); - case abi::msvc: - throw std::runtime_error("MSVC ABI JIT not implemented"); - case abi::armv8: - aarch64::compile(context, statements); - break; - } - } - void compile(program_context & pcontext, ir::module_context const & mcontext) { switch (pcontext.abi) diff --git a/plans.txt b/plans.txt index 8ad6084..81f03f1 100644 --- a/plans.txt +++ b/plans.txt @@ -1,7 +1,5 @@ Future plans: -* Split compiler into IR generation, IR optimization, and target-specific bytecode emitter * Globals (requires a separate mmaped segment in JIT compiler) -* Pointers: pointer types, address-of operator (&), dereferencing, scope-based lifetime tracking in interpreter * Function overloading: separate functions from values (again) in interpreter, allow casting to specific function type to take function value * Const propagation: annotate expression AST nodes that are computable in compile-time * Generic parameters: can be either values or `t : type`, but always compile-time @@ -13,19 +11,15 @@ Future plans: Interpreter backlog: * Fix identifier resolution for functions & variables +* Replace tree-walking interpreter with IR-based interpreter (invalidates the previous item) * C FFI (foreign functions) -Aarch64 compiler backlog: -* Rewrite using IR (compiler_v2.cpp first, then swap with the old one) -* Struct fields in structs (initialization & field access) -* Struct function arguments & return values -* Arrays +Aarch64 compiler backlog: (empty!) -IR backlog: -* Structs & arrays: structs indexed by field index, make sure the representation allows for SROA +Optimizer plans: * Inlining: track function IR size (number of nodes will do), substitute its code instead of calling if it is small enough (pay attention to recursion) * Constant folding & propagation: if all node arguments are `const` nodes, replace the current node with the computed value -* Arithmetic simplification: replace a+0 with a, etc +* Arithmetic simplification: replace a+0 with a, etc (maybe also replace `var+const+const` with `var+const` for array/struct access) * Branch removal: `if` nodes with condition nodes being `const` are replaced with unconditional jumps * Jump removal: `jump` that jumps to the immediate successor node is removed * Dead code elimination: DFS from function `return` nodes & impure function calls, remove all nodes not visited (make sure to not remove function arguments)