From 390a1943513df3c3e8104d3093ca6a19484c85e4 Mon Sep 17 00:00:00 2001 From: Lexy Plt Date: Sat, 11 Jul 2026 17:21:27 +0200 Subject: [PATCH 01/26] feat(compiler): ARK-325, track the origin of an ir block --- CHANGELOG.md | 1 + .../IntermediateRepresentation/Entity.hpp | 13 ++++++++- include/Ark/Compiler/Lowerer/ASTLowerer.hpp | 20 +++++++++----- .../IntermediateRepresentation/IRCompiler.cpp | 27 ++++++++++++++----- .../IROptimizer.cpp | 10 +++---- .../Compiler/Lowerer/ASTLowerer.cpp | 13 +++++---- .../CompilerSuite/ir/99bottles.expected | 2 +- .../CompilerSuite/ir/ackermann.expected | 4 +-- .../CompilerSuite/ir/args_attr.expected | 4 +-- .../CompilerSuite/ir/breakpoints.expected | 6 ++--- .../CompilerSuite/ir/closures.expected | 12 ++++----- .../CompilerSuite/ir/factorial.expected | 4 +-- .../ir/load_fast_by_index_after_cond.expected | 4 +-- .../CompilerSuite/ir/operators.expected | 2 +- .../ir/operators_as_builtins.expected | 2 +- .../CompilerSuite/ir/plugin.expected | 2 +- .../CompilerSuite/ir/ref_args.expected | 8 +++--- .../CompilerSuite/ir/renamed_capture.expected | 4 +-- .../CompilerSuite/ir/tail_call.expected | 4 +-- .../optimized_ir/99bottles.expected | 8 +++--- .../optimized_ir/ackermann.expected | 4 +-- .../optimized_ir/builtins.expected | 8 +++--- .../call_builtin_not_optimized.expected | 6 ++--- .../optimized_ir/closures.expected | 14 +++++----- .../optimized_ir/factorial.expected | 4 +-- .../optimized_ir/fused_math_ops.expected | 2 +- .../optimized_ir/increments.expected | 2 +- .../CompilerSuite/optimized_ir/jumps.expected | 4 +-- .../CompilerSuite/optimized_ir/lists.expected | 4 +-- .../CompilerSuite/optimized_ir/mul.expected | 4 +-- .../CompilerSuite/optimized_ir/type.expected | 4 +-- 31 files changed, 121 insertions(+), 85 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index edbcfc517..a8114bea1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ ### Changed - fix a bug related to recursive closures: once a closure was referenced in its own scope, we couldn't convert it to string or compare it against another closure/itself - bytecode reader: the length of code segments is counted in instructions, not bytes +- the compiler can track the origin of an IR block, in preparation for inlining ### Removed diff --git a/include/Ark/Compiler/IntermediateRepresentation/Entity.hpp b/include/Ark/Compiler/IntermediateRepresentation/Entity.hpp index 6f7bae73d..2791c03bc 100644 --- a/include/Ark/Compiler/IntermediateRepresentation/Entity.hpp +++ b/include/Ark/Compiler/IntermediateRepresentation/Entity.hpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include @@ -91,7 +92,17 @@ namespace Ark::internal::IR std::size_t m_source_line { 0 }; }; - using Block = std::vector; + struct Block + { + using vec_t = std::vector; + std::optional name; + vec_t data; + + std::string debugName() const + { + return name.value_or("#anonymous"); + } + }; } #endif // ARK_COMPILER_INTERMEDIATEREPRESENTATION_ENTITY_HPP diff --git a/include/Ark/Compiler/Lowerer/ASTLowerer.hpp b/include/Ark/Compiler/Lowerer/ASTLowerer.hpp index 26c08889b..8176443ed 100644 --- a/include/Ark/Compiler/Lowerer/ASTLowerer.hpp +++ b/include/Ark/Compiler/Lowerer/ASTLowerer.hpp @@ -123,11 +123,17 @@ namespace Ark::internal InvalidNodeInTailCallNoReturnValue }; - Page createNewCodePage(const bool temp = false) noexcept + struct PageCreationData { - if (!temp) + bool temp = false; + std::optional name = std::nullopt; + }; + + Page createNewCodePage(PageCreationData&& args = PageCreationData { .temp = false, .name = std::nullopt }) noexcept + { + if (!args.temp) { - m_code_pages.emplace_back(); + m_code_pages.emplace_back(args.name); return Page { .index = m_start_page_at_offset + m_code_pages.size() - 1u, .is_temp = false }; } @@ -139,13 +145,13 @@ namespace Ark::internal * @brief helper functions to get a temp or finalized code page * * @param page page descriptor - * @return std::vector& + * @return std::vector& */ - IR::Block& page(const Page page) noexcept + IR::Block::vec_t& page(const Page page) noexcept { if (!page.is_temp) - return m_code_pages[page.index - m_start_page_at_offset]; - return m_temp_pages[page.index]; + return m_code_pages[page.index - m_start_page_at_offset].data; + return m_temp_pages[page.index].data; } /** diff --git a/src/arkreactor/Compiler/IntermediateRepresentation/IRCompiler.cpp b/src/arkreactor/Compiler/IntermediateRepresentation/IRCompiler.cpp index 03fead37d..46bc9aa01 100644 --- a/src/arkreactor/Compiler/IntermediateRepresentation/IRCompiler.cpp +++ b/src/arkreactor/Compiler/IntermediateRepresentation/IRCompiler.cpp @@ -31,7 +31,7 @@ namespace Ark::internal // compute a list of unique filenames for (const auto& page : pages) { - for (const auto& inst : page) + for (const auto& inst : page.data) { if (std::ranges::find(m_filenames, inst.filename()) == m_filenames.end() && inst.hasValidSourceLocation()) m_filenames.push_back(inst.filename()); @@ -70,8 +70,13 @@ namespace Ark::internal std::size_t index = 0; for (const auto& block : m_ir) { - fmt::println(stream, "page_{}", index); - for (const auto& entity : block) + if (index == 0) + // global scope + fmt::println(stream, "page_{}", block.debugName()); + else + fmt::println(stream, "page_{} ({})", index, block.debugName()); + + for (const auto& entity : block.data) { switch (entity.kind()) { @@ -116,7 +121,7 @@ namespace Ark::internal // push the different code segments for (std::size_t i = 0, end = m_ir.size(); i < end; ++i) { - IR::Block& page = m_ir[i]; + IR::Block::vec_t& page = m_ir[i].data; // just in case we got too far, always add a HALT to be sure the // VM won't do anything crazy page.emplace_back(HALT); @@ -126,7 +131,17 @@ namespace Ark::internal return a.kind() != IR::Kind::Label; }); if (std::cmp_greater(page_size, MaxValue16Bits)) - throw std::overflow_error(fmt::format("Size of page {} exceeds the maximum size of {}", i, MaxValue16Bits)); + { + std::string message; + if (i == 0) + message = fmt::format("Global scope exceeds the maximum number of instructions ({})", MaxValue16Bits); + else if (m_ir[i].name.has_value()) + message = fmt::format("Function {} exceeds the maximum number of instructions ({})", m_ir[i].name.value(), MaxValue16Bits); + else + message = fmt::format("Anonymous function at page {} exceeds the maximum number of instructions ({})", i, MaxValue16Bits); + + throw std::overflow_error(message); + } m_bytecode.push_back(CODE_SEGMENT_START); serializeOn2BytesToVecBE(page_size, m_bytecode); @@ -304,7 +319,7 @@ namespace Ark::internal const auto& page = pages[i]; uint16_t ip = 0; - for (const auto& inst : page) + for (const auto& inst : page.data) { if (inst.hasValidSourceLocation()) { diff --git a/src/arkreactor/Compiler/IntermediateRepresentation/IROptimizer.cpp b/src/arkreactor/Compiler/IntermediateRepresentation/IROptimizer.cpp index 5ce10c145..6c8d4aa35 100644 --- a/src/arkreactor/Compiler/IntermediateRepresentation/IROptimizer.cpp +++ b/src/arkreactor/Compiler/IntermediateRepresentation/IROptimizer.cpp @@ -268,17 +268,17 @@ namespace Ark::internal for (const auto& block : pages) { m_ir.emplace_back(); - IR::Block& current_block = m_ir.back(); + std::vector& current_block = m_ir.back().data; std::size_t i = 0; - const std::size_t end = block.size(); + const std::size_t end = block.data.size(); while (i < end) { std::optional maybe_compacted = replaceWithRules( std::span( - block.begin() + static_cast(i), - block.size() - i), + block.data.begin() + static_cast(i), + block.data.size() - i), i); if (maybe_compacted.has_value()) @@ -289,7 +289,7 @@ namespace Ark::internal } else { - current_block.emplace_back(block[i]); + current_block.emplace_back(block.data[i]); ++i; } } diff --git a/src/arkreactor/Compiler/Lowerer/ASTLowerer.cpp b/src/arkreactor/Compiler/Lowerer/ASTLowerer.cpp index 18ebec92d..28de7736c 100644 --- a/src/arkreactor/Compiler/Lowerer/ASTLowerer.cpp +++ b/src/arkreactor/Compiler/Lowerer/ASTLowerer.cpp @@ -304,7 +304,6 @@ namespace Ark::internal else { // If we are here, we should have a function name via the m_opened_vars. - // Push arguments first, then function name, then call it. handleCalls(x, p, is_result_unused, is_terminal, can_use_ref); } } @@ -548,8 +547,12 @@ namespace Ark::internal ? LocalsLocator::ScopeType::Closure : LocalsLocator::ScopeType::Function); + std::optional page_name = std::nullopt; + if (!m_opened_vars.empty() && !x.isAnonymousFunction()) + page_name = m_opened_vars.top().name; + // create new page for function body - const auto function_body_page = createNewCodePage(); + const auto function_body_page = createNewCodePage({ .name = page_name }); // save page_id into the constants table as PageAddr and load the const page(p).emplace_back(is_closure ? MAKE_CLOSURE : LOAD_CONST, addValue(function_body_page.index, x)); @@ -574,7 +577,7 @@ namespace Ark::internal // Register an opened variable as "#anonymous", which won't match any valid names inside ASTLowerer::handleCalls. // This way we can continue to safely apply optimisations on // (let name (fun (e) (map lst (fun (e) (name e))))) - // Otherwise, `name` would have been optimized to a CALL_CURRENT_PAGE, which would have returned the wrong page. + // Otherwise, `name` would have been optimised to a CALL_CURRENT_PAGE, which would have returned the wrong page. if (x.isAnonymousFunction()) m_opened_vars.emplace("#anonymous", arg_count); // push body of the function @@ -903,7 +906,7 @@ namespace Ark::internal page(p).emplace_back(IR::Entity::Goto(label_return, PUSH_RETURN_ADDRESS)); page(p).back().setSourceLocation(x.filename(), x.position().start.line); - const Page proc_page = createNewCodePage(/* temp= */ true); + const Page proc_page = createNewCodePage({ .temp = true }); CallType call_type = CallType::Classic; std::optional call_arg = std::nullopt; @@ -978,7 +981,7 @@ namespace Ark::internal case CallType::SymbolByIndex: { - const Page temp_page = createNewCodePage(/* temp= */ true); + const Page temp_page = createNewCodePage({ .temp = true }); compileExpression(node, temp_page, false, false, true); assert(page(temp_page).size() == 1 && page(temp_page).back().inst() == LOAD_FAST_BY_INDEX); page(p).emplace_back(CALL_SYMBOL_BY_INDEX, page(temp_page).back().primaryArg(), args_count); diff --git a/tests/unittests/resources/CompilerSuite/ir/99bottles.expected b/tests/unittests/resources/CompilerSuite/ir/99bottles.expected index 3b4029571..9934fd7c4 100644 --- a/tests/unittests/resources/CompilerSuite/ir/99bottles.expected +++ b/tests/unittests/resources/CompilerSuite/ir/99bottles.expected @@ -1,4 +1,4 @@ -page_0 +page_#anonymous LOAD_FAST 1 STORE 0 LOAD_FAST_BY_INDEX 0 diff --git a/tests/unittests/resources/CompilerSuite/ir/ackermann.expected b/tests/unittests/resources/CompilerSuite/ir/ackermann.expected index 3cc59f03e..2af29ed2f 100644 --- a/tests/unittests/resources/CompilerSuite/ir/ackermann.expected +++ b/tests/unittests/resources/CompilerSuite/ir/ackermann.expected @@ -1,4 +1,4 @@ -page_0 +page_#anonymous LOAD_CONST 0 STORE 0 PUSH_RETURN_ADDRESS L5 @@ -13,7 +13,7 @@ page_0 POP 0 HALT 0 -page_1 +page_1 (ackermann) STORE 1 STORE 2 LOAD_FAST_BY_INDEX 0 diff --git a/tests/unittests/resources/CompilerSuite/ir/args_attr.expected b/tests/unittests/resources/CompilerSuite/ir/args_attr.expected index 943090788..42226b34a 100644 --- a/tests/unittests/resources/CompilerSuite/ir/args_attr.expected +++ b/tests/unittests/resources/CompilerSuite/ir/args_attr.expected @@ -1,4 +1,4 @@ -page_0 +page_#anonymous LOAD_CONST 0 STORE 0 LOAD_CONST 1 @@ -17,7 +17,7 @@ page_0 POP 0 HALT 0 -page_1 +page_1 (foo) STORE_REF 1 STORE 2 LOAD_FAST_BY_INDEX 1 diff --git a/tests/unittests/resources/CompilerSuite/ir/breakpoints.expected b/tests/unittests/resources/CompilerSuite/ir/breakpoints.expected index 0df999a30..fca6948b8 100644 --- a/tests/unittests/resources/CompilerSuite/ir/breakpoints.expected +++ b/tests/unittests/resources/CompilerSuite/ir/breakpoints.expected @@ -1,4 +1,4 @@ -page_0 +page_#anonymous BUILTIN 1 BREAKPOINT 1 LOAD_CONST 0 @@ -48,7 +48,7 @@ page_0 POP 0 HALT 0 -page_1 +page_1 (foo) STORE 1 STORE 2 STORE 3 @@ -92,7 +92,7 @@ page_1 RET 0 HALT 0 -page_2 +page_2 (bar) BUILTIN 2 RET 0 HALT 0 diff --git a/tests/unittests/resources/CompilerSuite/ir/closures.expected b/tests/unittests/resources/CompilerSuite/ir/closures.expected index 33a7cec5f..3ab1fc1be 100644 --- a/tests/unittests/resources/CompilerSuite/ir/closures.expected +++ b/tests/unittests/resources/CompilerSuite/ir/closures.expected @@ -1,4 +1,4 @@ -page_0 +page_#anonymous LOAD_CONST 0 STORE 0 PUSH_RETURN_ADDRESS L0 @@ -81,7 +81,7 @@ page_0 POP 0 HALT 0 -page_1 +page_1 (create-human) STORE 1 STORE 2 STORE 3 @@ -95,7 +95,7 @@ page_1 RET 0 HALT 0 -page_2 +page_2 (set-age) STORE 5 LOAD_FAST_BY_INDEX 0 SET_VAL 2 @@ -103,19 +103,19 @@ page_2 RET 0 HALT 0 -page_3 +page_3 (#anonymous) BUILTIN 2 RET 0 HALT 0 -page_4 +page_4 (countdown-from) STORE 9 CAPTURE 9 MAKE_CLOSURE 15 RET 0 HALT 0 -page_5 +page_5 (#anonymous) LOAD_FAST 9 LOAD_CONST 16 SUB 0 diff --git a/tests/unittests/resources/CompilerSuite/ir/factorial.expected b/tests/unittests/resources/CompilerSuite/ir/factorial.expected index 61151b24b..e4559488b 100644 --- a/tests/unittests/resources/CompilerSuite/ir/factorial.expected +++ b/tests/unittests/resources/CompilerSuite/ir/factorial.expected @@ -1,4 +1,4 @@ -page_0 +page_#anonymous LOAD_CONST 0 STORE 0 PUSH_RETURN_ADDRESS L2 @@ -12,7 +12,7 @@ page_0 POP 0 HALT 0 -page_1 +page_1 (fact) STORE 1 LOAD_CONST 1 STORE 2 diff --git a/tests/unittests/resources/CompilerSuite/ir/load_fast_by_index_after_cond.expected b/tests/unittests/resources/CompilerSuite/ir/load_fast_by_index_after_cond.expected index 426903429..3a9f03375 100644 --- a/tests/unittests/resources/CompilerSuite/ir/load_fast_by_index_after_cond.expected +++ b/tests/unittests/resources/CompilerSuite/ir/load_fast_by_index_after_cond.expected @@ -1,4 +1,4 @@ -page_0 +page_#anonymous LOAD_CONST 0 STORE 0 PUSH_RETURN_ADDRESS L3 @@ -13,7 +13,7 @@ page_0 POP 0 HALT 0 -page_1 +page_1 (foo) STORE 1 LOAD_CONST 1 STORE 2 diff --git a/tests/unittests/resources/CompilerSuite/ir/operators.expected b/tests/unittests/resources/CompilerSuite/ir/operators.expected index 0c3ee2703..e6920ce1b 100644 --- a/tests/unittests/resources/CompilerSuite/ir/operators.expected +++ b/tests/unittests/resources/CompilerSuite/ir/operators.expected @@ -1,4 +1,4 @@ -page_0 +page_#anonymous PUSH_RETURN_ADDRESS L0 LOAD_CONST 0 LOAD_CONST 1 diff --git a/tests/unittests/resources/CompilerSuite/ir/operators_as_builtins.expected b/tests/unittests/resources/CompilerSuite/ir/operators_as_builtins.expected index 1e508d6e5..09883de77 100644 --- a/tests/unittests/resources/CompilerSuite/ir/operators_as_builtins.expected +++ b/tests/unittests/resources/CompilerSuite/ir/operators_as_builtins.expected @@ -1,4 +1,4 @@ -page_0 +page_#anonymous PUSH_RETURN_ADDRESS L0 BUILTIN 90 BUILTIN 91 diff --git a/tests/unittests/resources/CompilerSuite/ir/plugin.expected b/tests/unittests/resources/CompilerSuite/ir/plugin.expected index 56d122e15..cd2c75fca 100644 --- a/tests/unittests/resources/CompilerSuite/ir/plugin.expected +++ b/tests/unittests/resources/CompilerSuite/ir/plugin.expected @@ -1,4 +1,4 @@ -page_0 +page_#anonymous PLUGIN 0 PUSH_RETURN_ADDRESS L0 LOAD_CONST 1 diff --git a/tests/unittests/resources/CompilerSuite/ir/ref_args.expected b/tests/unittests/resources/CompilerSuite/ir/ref_args.expected index b5cfea74c..a19df6fd6 100644 --- a/tests/unittests/resources/CompilerSuite/ir/ref_args.expected +++ b/tests/unittests/resources/CompilerSuite/ir/ref_args.expected @@ -1,4 +1,4 @@ -page_0 +page_#anonymous LOAD_CONST 0 STORE 0 PUSH_RETURN_ADDRESS L0 @@ -25,7 +25,7 @@ page_0 POP 0 HALT 0 -page_1 +page_1 (f) STORE 1 STORE 2 LOAD_SYMBOL 1 @@ -36,7 +36,7 @@ page_1 RET 0 HALT 0 -page_2 +page_2 (g) STORE 1 STORE 2 LOAD_SYMBOL 1 @@ -47,7 +47,7 @@ page_2 RET 0 HALT 0 -page_3 +page_3 (h) STORE 1 STORE 2 LOAD_CONST 5 diff --git a/tests/unittests/resources/CompilerSuite/ir/renamed_capture.expected b/tests/unittests/resources/CompilerSuite/ir/renamed_capture.expected index f85e13b2e..7a7707859 100644 --- a/tests/unittests/resources/CompilerSuite/ir/renamed_capture.expected +++ b/tests/unittests/resources/CompilerSuite/ir/renamed_capture.expected @@ -1,4 +1,4 @@ -page_0 +page_#anonymous LOAD_CONST 0 STORE 0 RENAME_NEXT_CAPTURE 2 @@ -12,7 +12,7 @@ page_0 POP 0 HALT 0 -page_1 +page_1 (b:child) BUILTIN 2 RET 0 HALT 0 diff --git a/tests/unittests/resources/CompilerSuite/ir/tail_call.expected b/tests/unittests/resources/CompilerSuite/ir/tail_call.expected index 222f464b3..5afe94e38 100644 --- a/tests/unittests/resources/CompilerSuite/ir/tail_call.expected +++ b/tests/unittests/resources/CompilerSuite/ir/tail_call.expected @@ -1,4 +1,4 @@ -page_0 +page_#anonymous LOAD_CONST 0 STORE 0 PUSH_RETURN_ADDRESS L3 @@ -9,7 +9,7 @@ page_0 POP 0 HALT 0 -page_1 +page_1 (f) STORE 1 STORE 2 LOAD_FAST_BY_INDEX 1 diff --git a/tests/unittests/resources/CompilerSuite/optimized_ir/99bottles.expected b/tests/unittests/resources/CompilerSuite/optimized_ir/99bottles.expected index 46e6552df..bc16a5b6a 100644 --- a/tests/unittests/resources/CompilerSuite/optimized_ir/99bottles.expected +++ b/tests/unittests/resources/CompilerSuite/optimized_ir/99bottles.expected @@ -1,4 +1,4 @@ -page_0 +page_#anonymous LOAD_CONST_STORE 0, 0 LOAD_CONST_STORE 1, 2 LOAD_CONST_STORE 2, 4 @@ -59,19 +59,19 @@ page_0 POP_SCOPE 0 HALT 0 -page_1 +page_1 (#anonymous) CALL_BUILTIN_WITHOUT_RETURN_ADDRESS 26, 1 .L0: RET 0 HALT 0 -page_2 +page_2 (#anonymous) CALL_BUILTIN_WITHOUT_RETURN_ADDRESS 27, 1 .L1: RET 0 HALT 0 -page_3 +page_3 (#anonymous) CALL_BUILTIN_WITHOUT_RETURN_ADDRESS 28, 1 .L2: RET 0 diff --git a/tests/unittests/resources/CompilerSuite/optimized_ir/ackermann.expected b/tests/unittests/resources/CompilerSuite/optimized_ir/ackermann.expected index e1798fff8..766931885 100644 --- a/tests/unittests/resources/CompilerSuite/optimized_ir/ackermann.expected +++ b/tests/unittests/resources/CompilerSuite/optimized_ir/ackermann.expected @@ -1,4 +1,4 @@ -page_0 +page_#anonymous LOAD_CONST_STORE 0, 0 PUSH_RETURN_ADDRESS L5 LOAD_CONST 3 @@ -11,7 +11,7 @@ page_0 POP 0 HALT 0 -page_1 +page_1 (#anonymous) STORE 1 STORE 2 LOAD_FAST_BY_INDEX 0 diff --git a/tests/unittests/resources/CompilerSuite/optimized_ir/builtins.expected b/tests/unittests/resources/CompilerSuite/optimized_ir/builtins.expected index 339db62b2..430019191 100644 --- a/tests/unittests/resources/CompilerSuite/optimized_ir/builtins.expected +++ b/tests/unittests/resources/CompilerSuite/optimized_ir/builtins.expected @@ -1,22 +1,22 @@ -page_0 +page_#anonymous LOAD_CONST_STORE 0, 0 LOAD_CONST_STORE 1, 2 LOAD_CONST_STORE 2, 4 HALT 0 -page_1 +page_1 (#anonymous) CALL_BUILTIN_WITHOUT_RETURN_ADDRESS 61, 1 .L0: RET 0 HALT 0 -page_2 +page_2 (#anonymous) CALL_BUILTIN_WITHOUT_RETURN_ADDRESS 4, 2 .L1: RET 0 HALT 0 -page_3 +page_3 (#anonymous) CALL_BUILTIN_WITHOUT_RETURN_ADDRESS 8, 3 .L2: RET 0 diff --git a/tests/unittests/resources/CompilerSuite/optimized_ir/call_builtin_not_optimized.expected b/tests/unittests/resources/CompilerSuite/optimized_ir/call_builtin_not_optimized.expected index dc510a521..8dd4861af 100644 --- a/tests/unittests/resources/CompilerSuite/optimized_ir/call_builtin_not_optimized.expected +++ b/tests/unittests/resources/CompilerSuite/optimized_ir/call_builtin_not_optimized.expected @@ -1,4 +1,4 @@ -page_0 +page_#anonymous LOAD_CONST_STORE 0, 0 PUSH_RETURN_ADDRESS L1 LOAD_CONST 1 @@ -13,7 +13,7 @@ page_0 POP 0 HALT 0 -page_1 +page_1 (#anonymous) STORE 1 PUSH_RETURN_ADDRESS L0 LOAD_FAST_BY_INDEX 0 @@ -27,7 +27,7 @@ page_1 RET 0 HALT 0 -page_2 +page_2 (#anonymous) CALL_BUILTIN_WITHOUT_RETURN_ADDRESS 9, 1 .L2: RET 0 diff --git a/tests/unittests/resources/CompilerSuite/optimized_ir/closures.expected b/tests/unittests/resources/CompilerSuite/optimized_ir/closures.expected index 10b488274..7d3d1878d 100644 --- a/tests/unittests/resources/CompilerSuite/optimized_ir/closures.expected +++ b/tests/unittests/resources/CompilerSuite/optimized_ir/closures.expected @@ -1,4 +1,4 @@ -page_0 +page_#anonymous LOAD_CONST_STORE 0, 0 PUSH_RETURN_ADDRESS L0 LOAD_CONST_LOAD_CONST 3, 4 @@ -51,7 +51,7 @@ page_0 LOAD_CONST_STORE 18, 11 HALT 0 -page_1 +page_1 (#anonymous) STORE 1 STORE 2 STORE 3 @@ -64,32 +64,32 @@ page_1 RET 0 HALT 0 -page_2 +page_2 (#anonymous) STORE 5 SET_VAL_FROM_INDEX 0, 2 LOAD_SYMBOL 2 RET 0 HALT 0 -page_3 +page_3 (#anonymous) BUILTIN 2 RET 0 HALT 0 -page_4 +page_4 (#anonymous) STORE 9 CAPTURE 9 MAKE_CLOSURE 15 RET 0 HALT 0 -page_5 +page_5 (#anonymous) DECREMENT_STORE 9, 1 LOAD_FAST 9 RET 0 HALT 0 -page_6 +page_6 (#anonymous) PUSH_RETURN_ADDRESS L8 LOAD_CONST 19 PUSH_RETURN_ADDRESS L9 diff --git a/tests/unittests/resources/CompilerSuite/optimized_ir/factorial.expected b/tests/unittests/resources/CompilerSuite/optimized_ir/factorial.expected index c75344f7f..6741dd2aa 100644 --- a/tests/unittests/resources/CompilerSuite/optimized_ir/factorial.expected +++ b/tests/unittests/resources/CompilerSuite/optimized_ir/factorial.expected @@ -1,4 +1,4 @@ -page_0 +page_#anonymous LOAD_CONST_STORE 0, 0 PUSH_RETURN_ADDRESS L2 LOAD_CONST 3 @@ -11,7 +11,7 @@ page_0 POP 0 HALT 0 -page_1 +page_1 (#anonymous) STORE 1 LOAD_CONST_STORE 1, 2 LOAD_CONST_STORE 2, 3 diff --git a/tests/unittests/resources/CompilerSuite/optimized_ir/fused_math_ops.expected b/tests/unittests/resources/CompilerSuite/optimized_ir/fused_math_ops.expected index 26bcca486..53141d307 100644 --- a/tests/unittests/resources/CompilerSuite/optimized_ir/fused_math_ops.expected +++ b/tests/unittests/resources/CompilerSuite/optimized_ir/fused_math_ops.expected @@ -1,4 +1,4 @@ -page_0 +page_#anonymous LOAD_CONST_STORE 0, 0 LOAD_CONST_STORE 1, 1 LOAD_CONST_STORE 2, 2 diff --git a/tests/unittests/resources/CompilerSuite/optimized_ir/increments.expected b/tests/unittests/resources/CompilerSuite/optimized_ir/increments.expected index a7401b229..a94e4a8ff 100644 --- a/tests/unittests/resources/CompilerSuite/optimized_ir/increments.expected +++ b/tests/unittests/resources/CompilerSuite/optimized_ir/increments.expected @@ -1,4 +1,4 @@ -page_0 +page_#anonymous LOAD_CONST_STORE 0, 0 INCREMENT_BY_INDEX 0, 4 SET_VAL 0 diff --git a/tests/unittests/resources/CompilerSuite/optimized_ir/jumps.expected b/tests/unittests/resources/CompilerSuite/optimized_ir/jumps.expected index 47797cf67..c4c372456 100644 --- a/tests/unittests/resources/CompilerSuite/optimized_ir/jumps.expected +++ b/tests/unittests/resources/CompilerSuite/optimized_ir/jumps.expected @@ -1,4 +1,4 @@ -page_0 +page_#anonymous LOAD_CONST_STORE 0, 0 LOAD_CONST_STORE 1, 2 CREATE_SCOPE 0 @@ -116,7 +116,7 @@ page_0 POP_SCOPE 0 HALT 0 -page_1 +page_1 (#anonymous) STORE 1 LOAD_FAST_BY_INDEX 0 RET 0 diff --git a/tests/unittests/resources/CompilerSuite/optimized_ir/lists.expected b/tests/unittests/resources/CompilerSuite/optimized_ir/lists.expected index c0211361b..fcd9ad606 100644 --- a/tests/unittests/resources/CompilerSuite/optimized_ir/lists.expected +++ b/tests/unittests/resources/CompilerSuite/optimized_ir/lists.expected @@ -1,4 +1,4 @@ -page_0 +page_#anonymous LOAD_CONST_LOAD_CONST 0, 1 LOAD_CONST_LOAD_CONST 2, 3 STORE_LIST 4, 0 @@ -27,7 +27,7 @@ page_0 APPEND_IN_PLACE_SYM_INDEX 1, 1 HALT 0 -page_1 +page_1 (#anonymous) STORE_HEAD 0, 4 STORE_TAIL 0, 5 STORE_FROM 0, 6 diff --git a/tests/unittests/resources/CompilerSuite/optimized_ir/mul.expected b/tests/unittests/resources/CompilerSuite/optimized_ir/mul.expected index 4719494a3..bca85ef78 100644 --- a/tests/unittests/resources/CompilerSuite/optimized_ir/mul.expected +++ b/tests/unittests/resources/CompilerSuite/optimized_ir/mul.expected @@ -1,4 +1,4 @@ -page_0 +page_#anonymous LOAD_CONST_STORE 0, 0 MUL_BY_INDEX 0, 2054 SET_VAL 0 @@ -11,7 +11,7 @@ page_0 POP 0 HALT 0 -page_1 +page_1 (#anonymous) MUL_BY 0, 2055 STORE 2 MUL_BY 0, 2055 diff --git a/tests/unittests/resources/CompilerSuite/optimized_ir/type.expected b/tests/unittests/resources/CompilerSuite/optimized_ir/type.expected index b864d6a6c..721e7ab03 100644 --- a/tests/unittests/resources/CompilerSuite/optimized_ir/type.expected +++ b/tests/unittests/resources/CompilerSuite/optimized_ir/type.expected @@ -1,4 +1,4 @@ -page_0 +page_#anonymous LOAD_CONST_STORE 0, 0 CHECK_TYPE_OF_BY_INDEX 0, 1 POP_JUMP_IF_TRUE L0 @@ -27,7 +27,7 @@ page_0 POP 0 HALT 0 -page_1 +page_1 (#anonymous) CHECK_TYPE_OF 0, 1 POP_JUMP_IF_TRUE L6 JUMP L7 From 25056706e398a844ea4de4f9cde132f18ed6b262 Mon Sep 17 00:00:00 2001 From: Lexy Plt Date: Sat, 11 Jul 2026 23:15:09 +0200 Subject: [PATCH 02/26] feat(compiler): ARK-325, add an empty ir inliner in between the ast lowerer and the ir optimizer --- .../IntermediateRepresentation/IRInliner.hpp | 37 +++++++++++++++++++ include/Ark/Compiler/Welder.hpp | 2 + .../IntermediateRepresentation/IRInliner.cpp | 25 +++++++++++++ src/arkreactor/Compiler/Welder.cpp | 5 +++ 4 files changed, 69 insertions(+) create mode 100644 include/Ark/Compiler/IntermediateRepresentation/IRInliner.hpp create mode 100644 src/arkreactor/Compiler/IntermediateRepresentation/IRInliner.cpp diff --git a/include/Ark/Compiler/IntermediateRepresentation/IRInliner.hpp b/include/Ark/Compiler/IntermediateRepresentation/IRInliner.hpp new file mode 100644 index 000000000..6a5f51630 --- /dev/null +++ b/include/Ark/Compiler/IntermediateRepresentation/IRInliner.hpp @@ -0,0 +1,37 @@ +/** + * @file IRInliner.hpp + * @author Lexy Plateau (lexplt.dev@gmail.com) + * @brief Try to inline IR blocks + * @date 2026-07-11 + * + * @copyright Copyright (c) 2026 + * + */ + +#ifndef ARK_COMPILER_INTERMEDIATEREPRESENTATION_IRINLINER_HPP +#define ARK_COMPILER_INTERMEDIATEREPRESENTATION_IRINLINER_HPP + +#include +#include +#include +#include + +namespace Ark::internal +{ + class ARK_API IRInliner final : public Pass + { + public: + explicit IRInliner(unsigned debug); + + void process(const std::vector& pages, const std::vector& symbols, const std::vector& values); + + const std::vector& intermediateRepresentation() const noexcept; + + private: + std::vector m_ir; + std::vector m_symbols; + std::vector m_values; + }; +} + +#endif // ARK_COMPILER_INTERMEDIATEREPRESENTATION_IRINLINER_HPP diff --git a/include/Ark/Compiler/Welder.hpp b/include/Ark/Compiler/Welder.hpp index 49339e41c..c43471c7a 100644 --- a/include/Ark/Compiler/Welder.hpp +++ b/include/Ark/Compiler/Welder.hpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -128,6 +129,7 @@ namespace Ark internal::Logger m_logger; internal::ASTLowerer m_lowerer; + internal::IRInliner m_ir_inliner; internal::IROptimizer m_ir_optimizer; internal::IRCompiler m_ir_compiler; diff --git a/src/arkreactor/Compiler/IntermediateRepresentation/IRInliner.cpp b/src/arkreactor/Compiler/IntermediateRepresentation/IRInliner.cpp new file mode 100644 index 000000000..f84bb656f --- /dev/null +++ b/src/arkreactor/Compiler/IntermediateRepresentation/IRInliner.cpp @@ -0,0 +1,25 @@ +#include + +namespace Ark::internal +{ + IRInliner::IRInliner(const unsigned debug) : + Pass("IRInliner", debug) + {} + + void IRInliner::process(const std::vector& pages, const std::vector& symbols, const std::vector& values) + { + m_logger.traceStart("process"); + m_symbols = symbols; + m_values = values; + + // TODO + m_ir = pages; + + m_logger.traceEnd(); + } + + const std::vector& IRInliner::intermediateRepresentation() const noexcept + { + return m_ir; + } +} diff --git a/src/arkreactor/Compiler/Welder.cpp b/src/arkreactor/Compiler/Welder.cpp index 41612d602..c3cd305bf 100644 --- a/src/arkreactor/Compiler/Welder.cpp +++ b/src/arkreactor/Compiler/Welder.cpp @@ -26,6 +26,7 @@ namespace Ark m_name_resolver(debug), m_logger("Welder", debug), m_lowerer(debug), + m_ir_inliner(debug), m_ir_optimizer(debug), m_ir_compiler(debug) {} @@ -68,6 +69,9 @@ namespace Ark if ((m_features & FeatureIROptimizer) != 0) { + m_ir_inliner.process(m_ir, m_lowerer.symbols(), m_lowerer.values()); + m_ir = m_ir_inliner.intermediateRepresentation(); + m_ir_optimizer.process(m_ir, m_lowerer.symbols(), m_lowerer.values()); m_ir = m_ir_optimizer.intermediateRepresentation(); } @@ -142,6 +146,7 @@ namespace Ark m_ast_optimizer.configureLogger(os); m_name_resolver.configureLogger(os); m_lowerer.configureLogger(os); + m_ir_inliner.configureLogger(os); m_ir_optimizer.configureLogger(os); m_ir_compiler.configureLogger(os); m_logger.configureOutputStream(&os); From 58848ef505a81bb8d7295da7f2c781adcee3b98d Mon Sep 17 00:00:00 2001 From: Lexy Plt Date: Sun, 12 Jul 2026 13:09:48 +0200 Subject: [PATCH 03/26] feat(compiler): ARK-325, add more metadata to IR blocks, and create a basic inlining heuristic --- .gitignore | 1 + .pre-commit-config.yaml | 5 +- CMakeLists.txt | 3 ++ .../IntermediateRepresentation/Entity.hpp | 47 +++++++++++++++---- .../IntermediateRepresentation/IRInliner.hpp | 28 ++++++++++- .../IROptimizer.hpp | 2 +- include/Ark/Compiler/Lowerer/ASTLowerer.hpp | 32 +++++++++---- .../IntermediateRepresentation/Entity.cpp | 11 +++-- .../IntermediateRepresentation/IRCompiler.cpp | 39 ++++++++++----- .../IntermediateRepresentation/IRInliner.cpp | 14 ++++++ .../Compiler/Lowerer/ASTLowerer.cpp | 37 ++++++++++++--- .../CompilerSuite/ir/99bottles.expected | 2 +- .../CompilerSuite/ir/ackermann.expected | 4 +- .../CompilerSuite/ir/args_attr.expected | 4 +- .../CompilerSuite/ir/breakpoints.expected | 6 +-- .../CompilerSuite/ir/closures.expected | 12 ++--- .../CompilerSuite/ir/factorial.expected | 4 +- .../ir/load_fast_by_index_after_cond.expected | 4 +- .../CompilerSuite/ir/operators.expected | 2 +- .../ir/operators_as_builtins.expected | 2 +- .../CompilerSuite/ir/plugin.expected | 2 +- .../CompilerSuite/ir/ref_args.expected | 8 ++-- .../CompilerSuite/ir/renamed_capture.expected | 4 +- .../CompilerSuite/ir/tail_call.expected | 4 +- .../optimized_ir/99bottles.expected | 8 ++-- .../optimized_ir/ackermann.expected | 4 +- .../optimized_ir/builtins.expected | 8 ++-- .../call_builtin_not_optimized.expected | 6 +-- .../optimized_ir/closures.expected | 14 +++--- .../optimized_ir/factorial.expected | 4 +- .../optimized_ir/fused_math_ops.expected | 2 +- .../optimized_ir/increments.expected | 2 +- .../CompilerSuite/optimized_ir/jumps.expected | 4 +- .../CompilerSuite/optimized_ir/lists.expected | 4 +- .../CompilerSuite/optimized_ir/mul.expected | 4 +- .../CompilerSuite/optimized_ir/type.expected | 4 +- 36 files changed, 239 insertions(+), 102 deletions(-) diff --git a/.gitignore b/.gitignore index a80088a57..4cacba7a4 100644 --- a/.gitignore +++ b/.gitignore @@ -41,6 +41,7 @@ include/Ark/Constants.hpp .cache/ build/ build_emscripten/ +cache-cppcheck/ ninja/ cmake-build-*/ out/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index eb6c8d8ec..a7914ef33 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -23,8 +23,9 @@ repos: "--suppressions-list=cppcheck-suppressions.txt", "--check-level=exhaustive", "--force", - "-I include", - "-j", "8" + "-Iinclude", + "-j", "8", + "--cppcheck-build-dir=cache-cppcheck" ] - repo: https://github.com/compilerla/conventional-pre-commit rev: 'v3.2.0' diff --git a/CMakeLists.txt b/CMakeLists.txt index e0b0ef511..6663bb373 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -25,6 +25,9 @@ else () set(ARK_BUILD_DATE "2026-01-02T17:56:00Z") endif () +# needed so that cppcheck has a cache folder, when launched through pre-commit +file(MAKE_DIRECTORY cache-cppcheck) + option(ARK_BUILD_EXE "Build a standalone arkscript executable" Off) option(ARK_ENABLE_SYSTEM "Enable sys:exec" On) # enable use of (sys:exec "command here") option(ARK_STATIC "Build ArkScript statically" Off) diff --git a/include/Ark/Compiler/IntermediateRepresentation/Entity.hpp b/include/Ark/Compiler/IntermediateRepresentation/Entity.hpp index 2791c03bc..128196c1c 100644 --- a/include/Ark/Compiler/IntermediateRepresentation/Entity.hpp +++ b/include/Ark/Compiler/IntermediateRepresentation/Entity.hpp @@ -38,6 +38,8 @@ namespace Ark::internal::IR constexpr uint16_t MaxValueForSmallNumber = 0x0800; static_assert(MaxValueForSmallNumber + MaxValueForSmallNumber - 1 == MaxValueForDualArg); + constexpr std::string AnonymousBlockName = "#anonymous"; + class Entity { public: @@ -75,32 +77,61 @@ namespace Ark::internal::IR void setSourceLocation(const std::string& filename, std::size_t line); - [[nodiscard]] bool hasValidSourceLocation() const { return !m_source_file.empty(); } + void setOriginalSymbolId(std::optional id); + + [[nodiscard]] bool hasValidSourceLocation() const { return !m_metadata.source_file.empty(); } + + [[nodiscard]] const std::string& filename() const { return m_metadata.source_file; } - [[nodiscard]] const std::string& filename() const { return m_source_file; } + [[nodiscard]] std::size_t sourceLine() const { return m_metadata.source_line; } - [[nodiscard]] std::size_t sourceLine() const { return m_source_line; } + [[nodiscard]] std::optional originalSymbolId() const { return m_metadata.symbol_id; } private: Kind m_kind; label_t m_label { 0 }; + Instruction m_inst { NOP }; uint16_t m_primary_arg { 0 }; uint16_t m_secondary_arg { 0 }; uint16_t m_tertiary_arg { 0 }; - std::string m_source_file; - std::size_t m_source_line { 0 }; + + struct + { + std::string source_file; + std::size_t source_line { 0 }; + std::optional symbol_id; ///< Used for LOAD_SYMBOL_BY_INDEX to know the original symbol id and deoptimize when necessary + } m_metadata; }; struct Block { using vec_t = std::vector; - std::optional name; + + struct Metadata + { + std::optional name; + std::size_t argument_count { 0 }; + bool is_closure { false }; + bool is_recursive { false }; + bool is_simple { false }; ///< Calls only builtin and operators, no user functions/C++ functions + } metadata; vec_t data; - std::string debugName() const + [[nodiscard]] std::string debugName() const + { + return metadata.name.value_or(AnonymousBlockName); + } + + [[nodiscard]] std::size_t instructionCount() const { - return name.value_or("#anonymous"); + const auto length = std::ranges::count_if(data, [](const auto& a) { + return a.kind() != IR::Kind::Label; + }); + + if (length <= 0) + return 0; + return static_cast(length); } }; } diff --git a/include/Ark/Compiler/IntermediateRepresentation/IRInliner.hpp b/include/Ark/Compiler/IntermediateRepresentation/IRInliner.hpp index 6a5f51630..d67a602b1 100644 --- a/include/Ark/Compiler/IntermediateRepresentation/IRInliner.hpp +++ b/include/Ark/Compiler/IntermediateRepresentation/IRInliner.hpp @@ -21,16 +21,42 @@ namespace Ark::internal class ARK_API IRInliner final : public Pass { public: + /** + * @brief Create a new IRInliner + * + * @param debug debug level + */ explicit IRInliner(unsigned debug); + /** + * @brief Attempt to inline IR blocks to avoid function calls when possible + * + * @param pages list of lists of IR entities generated by the compiler + * @param symbols symbol table generated by the compiler + * @param values value table generated by the compiler + */ void process(const std::vector& pages, const std::vector& symbols, const std::vector& values); - const std::vector& intermediateRepresentation() const noexcept; + /** + * @brief Return the IR blocks (one per scope) + * + * @return const std::vector& + */ + [[nodiscard]] const std::vector& intermediateRepresentation() const noexcept; private: std::vector m_ir; std::vector m_symbols; std::vector m_values; + + /** + * @brief Check if a block can be inlined in another one + * + * @param candidate block to inline + * @param source where the inlining will take place + * @return true if the candidate can be inlined + */ + [[nodiscard]] static bool canBeInlined(const IR::Block& candidate, const IR::Block& source) noexcept; }; } diff --git a/include/Ark/Compiler/IntermediateRepresentation/IROptimizer.hpp b/include/Ark/Compiler/IntermediateRepresentation/IROptimizer.hpp index 4bfd41e5e..6fc0b8b83 100644 --- a/include/Ark/Compiler/IntermediateRepresentation/IROptimizer.hpp +++ b/include/Ark/Compiler/IntermediateRepresentation/IROptimizer.hpp @@ -94,7 +94,7 @@ namespace Ark::internal [[nodiscard]] bool match(const std::vector& expected_insts, std::span entities) const; [[nodiscard]] bool canBeOptimizedSafely(std::span entities, std::size_t window_size) const; - std::optional replaceWithRules(std::span entities, const std::size_t position_in_block); + std::optional replaceWithRules(std::span entities, std::size_t position_in_block); [[nodiscard]] bool isPositiveNumberInlinable(uint16_t id) const; [[nodiscard]] bool isSmallerNumberInlinable(uint16_t id) const; diff --git a/include/Ark/Compiler/Lowerer/ASTLowerer.hpp b/include/Ark/Compiler/Lowerer/ASTLowerer.hpp index 8176443ed..766fb295b 100644 --- a/include/Ark/Compiler/Lowerer/ASTLowerer.hpp +++ b/include/Ark/Compiler/Lowerer/ASTLowerer.hpp @@ -34,6 +34,13 @@ namespace Ark namespace Ark::internal { + struct PageCreationData + { + bool temp { false }; + bool closure { false }; + std::optional name { std::nullopt }; + }; + /** * @brief The ArkScript AST to IR compiler * @@ -123,17 +130,16 @@ namespace Ark::internal InvalidNodeInTailCallNoReturnValue }; - struct PageCreationData - { - bool temp = false; - std::optional name = std::nullopt; - }; - - Page createNewCodePage(PageCreationData&& args = PageCreationData { .temp = false, .name = std::nullopt }) noexcept + Page createNewCodePage(PageCreationData&& args = PageCreationData {}) noexcept { if (!args.temp) { - m_code_pages.emplace_back(args.name); + m_code_pages.emplace_back( + IR::Block::Metadata { + .name = args.name, + .argument_count = 0, + .is_closure = args.closure }, + IR::Block::vec_t {}); return Page { .index = m_start_page_at_offset + m_code_pages.size() - 1u, .is_temp = false }; } @@ -141,8 +147,15 @@ namespace Ark::internal return Page { .index = m_temp_pages.size() - 1u, .is_temp = true }; } + IR::Block& block(const Page page) noexcept + { + if (!page.is_temp) + return m_code_pages[page.index - m_start_page_at_offset]; + return m_temp_pages[page.index]; + } + /** - * @brief helper functions to get a temp or finalized code page + * @brief helper functions to get a temp or finalised code page * * @param page page descriptor * @return std::vector& @@ -270,6 +283,7 @@ namespace Ark::internal void compileApplyInstruction(Node& x, Page p, bool is_result_unused); void compileIf(Node& x, Page p, bool is_result_unused, bool is_terminal, bool can_use_ref); void compileFunction(Node& x, Page p, bool is_result_unused); + void setFunctionMetadata(Page p, std::size_t arg_count); void compileLetMutSet(Keyword n, Node& x, Page p, bool is_result_unused); void compileWhile(Node& x, Page p); void compilePluginImport(const Node& x, Page p); diff --git a/src/arkreactor/Compiler/IntermediateRepresentation/Entity.cpp b/src/arkreactor/Compiler/IntermediateRepresentation/Entity.cpp index 972de2e56..b44723bc8 100644 --- a/src/arkreactor/Compiler/IntermediateRepresentation/Entity.cpp +++ b/src/arkreactor/Compiler/IntermediateRepresentation/Entity.cpp @@ -74,9 +74,14 @@ namespace Ark::internal::IR return Word(0, 0); } - void Entity::setSourceLocation(const std::string& filename, std::size_t line) + void Entity::setSourceLocation(const std::string& filename, const std::size_t line) { - m_source_file = filename; - m_source_line = line; + m_metadata.source_file = filename; + m_metadata.source_line = line; + } + + void Entity::setOriginalSymbolId(const std::optional id) + { + m_metadata.symbol_id = id; } } diff --git a/src/arkreactor/Compiler/IntermediateRepresentation/IRCompiler.cpp b/src/arkreactor/Compiler/IntermediateRepresentation/IRCompiler.cpp index 46bc9aa01..74f52fae6 100644 --- a/src/arkreactor/Compiler/IntermediateRepresentation/IRCompiler.cpp +++ b/src/arkreactor/Compiler/IntermediateRepresentation/IRCompiler.cpp @@ -72,9 +72,28 @@ namespace Ark::internal { if (index == 0) // global scope - fmt::println(stream, "page_{}", block.debugName()); + fmt::println(stream, "global"); else - fmt::println(stream, "page_{} ({})", index, block.debugName()); + { + std::string flags; + if (block.metadata.is_recursive) + flags += " recursive"; + if (block.metadata.is_simple) + flags += " simple"; + if (block.metadata.is_closure) + flags += " closure"; + else + flags += " function"; + + fmt::println( + stream, + "page_{} ({} ({} argument{}){})", + index, + block.debugName(), + block.metadata.argument_count, + block.metadata.argument_count == 1 ? "" : "s", + flags); + } for (const auto& entity : block.data) { @@ -121,22 +140,20 @@ namespace Ark::internal // push the different code segments for (std::size_t i = 0, end = m_ir.size(); i < end; ++i) { - IR::Block::vec_t& page = m_ir[i].data; + IR::Block& page = m_ir[i]; // just in case we got too far, always add a HALT to be sure the // VM won't do anything crazy - page.emplace_back(HALT); + page.data.emplace_back(HALT); // push number of elements - const auto page_size = std::ranges::count_if(page, [](const auto& a) { - return a.kind() != IR::Kind::Label; - }); + const std::size_t page_size = page.instructionCount(); if (std::cmp_greater(page_size, MaxValue16Bits)) { std::string message; if (i == 0) message = fmt::format("Global scope exceeds the maximum number of instructions ({})", MaxValue16Bits); - else if (m_ir[i].name.has_value()) - message = fmt::format("Function {} exceeds the maximum number of instructions ({})", m_ir[i].name.value(), MaxValue16Bits); + else if (page.metadata.name.has_value()) + message = fmt::format("Function {} exceeds the maximum number of instructions ({})", page.metadata.name.value(), MaxValue16Bits); else message = fmt::format("Anonymous function at page {} exceeds the maximum number of instructions ({})", i, MaxValue16Bits); @@ -149,7 +166,7 @@ namespace Ark::internal // register labels position uint16_t pos = 0; std::unordered_map label_to_position; - for (auto& inst : page) + for (const auto& inst : page.data) { switch (inst.kind()) { @@ -162,7 +179,7 @@ namespace Ark::internal } } - for (auto& inst : page) + for (const auto& inst : page.data) { switch (inst.kind()) { diff --git a/src/arkreactor/Compiler/IntermediateRepresentation/IRInliner.cpp b/src/arkreactor/Compiler/IntermediateRepresentation/IRInliner.cpp index f84bb656f..3fa3f696c 100644 --- a/src/arkreactor/Compiler/IntermediateRepresentation/IRInliner.cpp +++ b/src/arkreactor/Compiler/IntermediateRepresentation/IRInliner.cpp @@ -22,4 +22,18 @@ namespace Ark::internal { return m_ir; } + + bool IRInliner::canBeInlined(const IR::Block& candidate, const IR::Block& source) noexcept + { + const std::size_t candidate_inst_count = candidate.instructionCount(), + source_inst_count = source.instructionCount(); + + if (candidate.metadata.is_closure || + candidate.metadata.is_recursive || + candidate.metadata.name.value_or(IR::AnonymousBlockName) == IR::AnonymousBlockName || + std::cmp_greater_equal(candidate_inst_count + source_inst_count, MaxValue16Bits)) + return false; + // TODO: create a proper constant and make this less arbitrary? + return candidate.metadata.is_simple && candidate_inst_count < 24; + } } diff --git a/src/arkreactor/Compiler/Lowerer/ASTLowerer.cpp b/src/arkreactor/Compiler/Lowerer/ASTLowerer.cpp index 28de7736c..ac395b680 100644 --- a/src/arkreactor/Compiler/Lowerer/ASTLowerer.cpp +++ b/src/arkreactor/Compiler/Lowerer/ASTLowerer.cpp @@ -332,10 +332,11 @@ namespace Ark::internal if (can_use_ref) { const std::optional maybe_local_idx = m_locals_locator.lookupLastScopeByName(name); + const uint16_t symbol_id = addSymbol(x); if (maybe_local_idx.has_value()) - page(p).emplace_back(LOAD_FAST_BY_INDEX, static_cast(maybe_local_idx.value())); + page(p).emplace_back(LOAD_FAST_BY_INDEX, static_cast(maybe_local_idx.value())).setOriginalSymbolId(symbol_id); else - page(p).emplace_back(LOAD_FAST, addSymbol(x)); + page(p).emplace_back(LOAD_FAST, symbol_id); } else page(p).emplace_back(LOAD_SYMBOL, addSymbol(x)); @@ -552,7 +553,9 @@ namespace Ark::internal page_name = m_opened_vars.top().name; // create new page for function body - const auto function_body_page = createNewCodePage({ .name = page_name }); + const Page function_body_page = createNewCodePage( + { .closure = is_closure, + .name = page_name }); // save page_id into the constants table as PageAddr and load the const page(p).emplace_back(is_closure ? MAKE_CLOSURE : LOAD_CONST, addValue(function_body_page.index, x)); @@ -579,7 +582,7 @@ namespace Ark::internal // (let name (fun (e) (map lst (fun (e) (name e))))) // Otherwise, `name` would have been optimised to a CALL_CURRENT_PAGE, which would have returned the wrong page. if (x.isAnonymousFunction()) - m_opened_vars.emplace("#anonymous", arg_count); + m_opened_vars.emplace(IR::AnonymousBlockName, arg_count); // push body of the function compileExpression(x.list()[2], function_body_page, false, true, true); if (x.isAnonymousFunction()) @@ -589,6 +592,9 @@ namespace Ark::internal page(function_body_page).emplace_back(RET); m_locals_locator.deleteScope(); + // needed for the IRInliner + setFunctionMetadata(function_body_page, arg_count); + // if the computed function is unused, pop it if (is_result_unused) { @@ -597,6 +603,25 @@ namespace Ark::internal } } + void ASTLowerer::setFunctionMetadata(const Page p, const std::size_t arg_count) + { + bool is_recursive = false; + bool is_simple = true; + + for (const IR::Entity& e : page(p)) + { + if (e.inst() == TAIL_CALL_SELF || e.inst() == CALL_CURRENT_PAGE) + is_recursive = true; + if (e.inst() == APPLY || e.inst() == CALL || e.inst() == CALL_SYMBOL || e.inst() == CALL_SYMBOL_BY_INDEX || + e.inst() == MAKE_CLOSURE) + is_simple = false; + } + + block(p).metadata.argument_count = arg_count; + block(p).metadata.is_recursive = is_recursive; + block(p).metadata.is_simple = is_simple; + } + void ASTLowerer::compileLetMutSet(const Keyword n, Node& x, const Page p, const bool is_result_unused) { if (const auto sym = x.constList()[1]; sym.nodeType() != NodeType::Symbol) @@ -669,7 +694,7 @@ namespace Ark::internal // reset the scope at the end of the loop so that indices are still valid // otherwise, (while true { (let a 5) (print a) (let b 6) (print b) }) - // would print 5, 6, then only 6 as we emit LOAD_SYMBOL_FROM_INDEX 0 and b is the last in the scope + // would print 5, 6, then only 6 as we emit LOAD_FAST_BY_INDEX 0 and b is the last in the scope // loop, jump to the condition page(p).emplace_back(IR::Entity::Goto(label_loop, RESET_SCOPE_JUMP)); @@ -984,7 +1009,7 @@ namespace Ark::internal const Page temp_page = createNewCodePage({ .temp = true }); compileExpression(node, temp_page, false, false, true); assert(page(temp_page).size() == 1 && page(temp_page).back().inst() == LOAD_FAST_BY_INDEX); - page(p).emplace_back(CALL_SYMBOL_BY_INDEX, page(temp_page).back().primaryArg(), args_count); + page(p).emplace_back(CALL_SYMBOL_BY_INDEX, page(temp_page).back().primaryArg(), args_count).setOriginalSymbolId(page(temp_page).back().originalSymbolId()); m_temp_pages.pop_back(); break; } diff --git a/tests/unittests/resources/CompilerSuite/ir/99bottles.expected b/tests/unittests/resources/CompilerSuite/ir/99bottles.expected index 9934fd7c4..c7ba9837e 100644 --- a/tests/unittests/resources/CompilerSuite/ir/99bottles.expected +++ b/tests/unittests/resources/CompilerSuite/ir/99bottles.expected @@ -1,4 +1,4 @@ -page_#anonymous +global LOAD_FAST 1 STORE 0 LOAD_FAST_BY_INDEX 0 diff --git a/tests/unittests/resources/CompilerSuite/ir/ackermann.expected b/tests/unittests/resources/CompilerSuite/ir/ackermann.expected index 2af29ed2f..af1aac234 100644 --- a/tests/unittests/resources/CompilerSuite/ir/ackermann.expected +++ b/tests/unittests/resources/CompilerSuite/ir/ackermann.expected @@ -1,4 +1,4 @@ -page_#anonymous +global LOAD_CONST 0 STORE 0 PUSH_RETURN_ADDRESS L5 @@ -13,7 +13,7 @@ page_#anonymous POP 0 HALT 0 -page_1 (ackermann) +page_1 (ackermann (2 arguments) recursive simple function) STORE 1 STORE 2 LOAD_FAST_BY_INDEX 0 diff --git a/tests/unittests/resources/CompilerSuite/ir/args_attr.expected b/tests/unittests/resources/CompilerSuite/ir/args_attr.expected index 42226b34a..5e549039a 100644 --- a/tests/unittests/resources/CompilerSuite/ir/args_attr.expected +++ b/tests/unittests/resources/CompilerSuite/ir/args_attr.expected @@ -1,4 +1,4 @@ -page_#anonymous +global LOAD_CONST 0 STORE 0 LOAD_CONST 1 @@ -17,7 +17,7 @@ page_#anonymous POP 0 HALT 0 -page_1 (foo) +page_1 (foo (2 arguments) simple function) STORE_REF 1 STORE 2 LOAD_FAST_BY_INDEX 1 diff --git a/tests/unittests/resources/CompilerSuite/ir/breakpoints.expected b/tests/unittests/resources/CompilerSuite/ir/breakpoints.expected index fca6948b8..36c2ddeb0 100644 --- a/tests/unittests/resources/CompilerSuite/ir/breakpoints.expected +++ b/tests/unittests/resources/CompilerSuite/ir/breakpoints.expected @@ -1,4 +1,4 @@ -page_#anonymous +global BUILTIN 1 BREAKPOINT 1 LOAD_CONST 0 @@ -48,7 +48,7 @@ page_#anonymous POP 0 HALT 0 -page_1 (foo) +page_1 (foo (3 arguments) simple function) STORE 1 STORE 2 STORE 3 @@ -92,7 +92,7 @@ page_1 (foo) RET 0 HALT 0 -page_2 (bar) +page_2 (bar (0 arguments) simple function) BUILTIN 2 RET 0 HALT 0 diff --git a/tests/unittests/resources/CompilerSuite/ir/closures.expected b/tests/unittests/resources/CompilerSuite/ir/closures.expected index 3ab1fc1be..3753c5f22 100644 --- a/tests/unittests/resources/CompilerSuite/ir/closures.expected +++ b/tests/unittests/resources/CompilerSuite/ir/closures.expected @@ -1,4 +1,4 @@ -page_#anonymous +global LOAD_CONST 0 STORE 0 PUSH_RETURN_ADDRESS L0 @@ -81,7 +81,7 @@ page_#anonymous POP 0 HALT 0 -page_1 (create-human) +page_1 (create-human (3 arguments) function) STORE 1 STORE 2 STORE 3 @@ -95,7 +95,7 @@ page_1 (create-human) RET 0 HALT 0 -page_2 (set-age) +page_2 (set-age (1 argument) simple function) STORE 5 LOAD_FAST_BY_INDEX 0 SET_VAL 2 @@ -103,19 +103,19 @@ page_2 (set-age) RET 0 HALT 0 -page_3 (#anonymous) +page_3 (#anonymous (0 arguments) simple closure) BUILTIN 2 RET 0 HALT 0 -page_4 (countdown-from) +page_4 (countdown-from (1 argument) function) STORE 9 CAPTURE 9 MAKE_CLOSURE 15 RET 0 HALT 0 -page_5 (#anonymous) +page_5 (#anonymous (0 arguments) simple closure) LOAD_FAST 9 LOAD_CONST 16 SUB 0 diff --git a/tests/unittests/resources/CompilerSuite/ir/factorial.expected b/tests/unittests/resources/CompilerSuite/ir/factorial.expected index e4559488b..8d1f6c4f4 100644 --- a/tests/unittests/resources/CompilerSuite/ir/factorial.expected +++ b/tests/unittests/resources/CompilerSuite/ir/factorial.expected @@ -1,4 +1,4 @@ -page_#anonymous +global LOAD_CONST 0 STORE 0 PUSH_RETURN_ADDRESS L2 @@ -12,7 +12,7 @@ page_#anonymous POP 0 HALT 0 -page_1 (fact) +page_1 (fact (1 argument) simple function) STORE 1 LOAD_CONST 1 STORE 2 diff --git a/tests/unittests/resources/CompilerSuite/ir/load_fast_by_index_after_cond.expected b/tests/unittests/resources/CompilerSuite/ir/load_fast_by_index_after_cond.expected index 3a9f03375..63b82a4b2 100644 --- a/tests/unittests/resources/CompilerSuite/ir/load_fast_by_index_after_cond.expected +++ b/tests/unittests/resources/CompilerSuite/ir/load_fast_by_index_after_cond.expected @@ -1,4 +1,4 @@ -page_#anonymous +global LOAD_CONST 0 STORE 0 PUSH_RETURN_ADDRESS L3 @@ -13,7 +13,7 @@ page_#anonymous POP 0 HALT 0 -page_1 (foo) +page_1 (foo (1 argument) simple function) STORE 1 LOAD_CONST 1 STORE 2 diff --git a/tests/unittests/resources/CompilerSuite/ir/operators.expected b/tests/unittests/resources/CompilerSuite/ir/operators.expected index e6920ce1b..6c05a1bc6 100644 --- a/tests/unittests/resources/CompilerSuite/ir/operators.expected +++ b/tests/unittests/resources/CompilerSuite/ir/operators.expected @@ -1,4 +1,4 @@ -page_#anonymous +global PUSH_RETURN_ADDRESS L0 LOAD_CONST 0 LOAD_CONST 1 diff --git a/tests/unittests/resources/CompilerSuite/ir/operators_as_builtins.expected b/tests/unittests/resources/CompilerSuite/ir/operators_as_builtins.expected index 09883de77..2128fa104 100644 --- a/tests/unittests/resources/CompilerSuite/ir/operators_as_builtins.expected +++ b/tests/unittests/resources/CompilerSuite/ir/operators_as_builtins.expected @@ -1,4 +1,4 @@ -page_#anonymous +global PUSH_RETURN_ADDRESS L0 BUILTIN 90 BUILTIN 91 diff --git a/tests/unittests/resources/CompilerSuite/ir/plugin.expected b/tests/unittests/resources/CompilerSuite/ir/plugin.expected index cd2c75fca..f64fd63c9 100644 --- a/tests/unittests/resources/CompilerSuite/ir/plugin.expected +++ b/tests/unittests/resources/CompilerSuite/ir/plugin.expected @@ -1,4 +1,4 @@ -page_#anonymous +global PLUGIN 0 PUSH_RETURN_ADDRESS L0 LOAD_CONST 1 diff --git a/tests/unittests/resources/CompilerSuite/ir/ref_args.expected b/tests/unittests/resources/CompilerSuite/ir/ref_args.expected index a19df6fd6..26c61a553 100644 --- a/tests/unittests/resources/CompilerSuite/ir/ref_args.expected +++ b/tests/unittests/resources/CompilerSuite/ir/ref_args.expected @@ -1,4 +1,4 @@ -page_#anonymous +global LOAD_CONST 0 STORE 0 PUSH_RETURN_ADDRESS L0 @@ -25,7 +25,7 @@ page_#anonymous POP 0 HALT 0 -page_1 (f) +page_1 (f (2 arguments) recursive simple function) STORE 1 STORE 2 LOAD_SYMBOL 1 @@ -36,7 +36,7 @@ page_1 (f) RET 0 HALT 0 -page_2 (g) +page_2 (g (2 arguments) recursive simple function) STORE 1 STORE 2 LOAD_SYMBOL 1 @@ -47,7 +47,7 @@ page_2 (g) RET 0 HALT 0 -page_3 (h) +page_3 (h (2 arguments) recursive simple function) STORE 1 STORE 2 LOAD_CONST 5 diff --git a/tests/unittests/resources/CompilerSuite/ir/renamed_capture.expected b/tests/unittests/resources/CompilerSuite/ir/renamed_capture.expected index 7a7707859..7de32746c 100644 --- a/tests/unittests/resources/CompilerSuite/ir/renamed_capture.expected +++ b/tests/unittests/resources/CompilerSuite/ir/renamed_capture.expected @@ -1,4 +1,4 @@ -page_#anonymous +global LOAD_CONST 0 STORE 0 RENAME_NEXT_CAPTURE 2 @@ -12,7 +12,7 @@ page_#anonymous POP 0 HALT 0 -page_1 (b:child) +page_1 (b:child (0 arguments) simple closure) BUILTIN 2 RET 0 HALT 0 diff --git a/tests/unittests/resources/CompilerSuite/ir/tail_call.expected b/tests/unittests/resources/CompilerSuite/ir/tail_call.expected index 5afe94e38..85b818f8f 100644 --- a/tests/unittests/resources/CompilerSuite/ir/tail_call.expected +++ b/tests/unittests/resources/CompilerSuite/ir/tail_call.expected @@ -1,4 +1,4 @@ -page_#anonymous +global LOAD_CONST 0 STORE 0 PUSH_RETURN_ADDRESS L3 @@ -9,7 +9,7 @@ page_#anonymous POP 0 HALT 0 -page_1 (f) +page_1 (f (2 arguments) recursive simple function) STORE 1 STORE 2 LOAD_FAST_BY_INDEX 1 diff --git a/tests/unittests/resources/CompilerSuite/optimized_ir/99bottles.expected b/tests/unittests/resources/CompilerSuite/optimized_ir/99bottles.expected index bc16a5b6a..55ddc1cbe 100644 --- a/tests/unittests/resources/CompilerSuite/optimized_ir/99bottles.expected +++ b/tests/unittests/resources/CompilerSuite/optimized_ir/99bottles.expected @@ -1,4 +1,4 @@ -page_#anonymous +global LOAD_CONST_STORE 0, 0 LOAD_CONST_STORE 1, 2 LOAD_CONST_STORE 2, 4 @@ -59,19 +59,19 @@ page_#anonymous POP_SCOPE 0 HALT 0 -page_1 (#anonymous) +page_1 (#anonymous (0 arguments) function) CALL_BUILTIN_WITHOUT_RETURN_ADDRESS 26, 1 .L0: RET 0 HALT 0 -page_2 (#anonymous) +page_2 (#anonymous (0 arguments) function) CALL_BUILTIN_WITHOUT_RETURN_ADDRESS 27, 1 .L1: RET 0 HALT 0 -page_3 (#anonymous) +page_3 (#anonymous (0 arguments) function) CALL_BUILTIN_WITHOUT_RETURN_ADDRESS 28, 1 .L2: RET 0 diff --git a/tests/unittests/resources/CompilerSuite/optimized_ir/ackermann.expected b/tests/unittests/resources/CompilerSuite/optimized_ir/ackermann.expected index 766931885..0b44348e5 100644 --- a/tests/unittests/resources/CompilerSuite/optimized_ir/ackermann.expected +++ b/tests/unittests/resources/CompilerSuite/optimized_ir/ackermann.expected @@ -1,4 +1,4 @@ -page_#anonymous +global LOAD_CONST_STORE 0, 0 PUSH_RETURN_ADDRESS L5 LOAD_CONST 3 @@ -11,7 +11,7 @@ page_#anonymous POP 0 HALT 0 -page_1 (#anonymous) +page_1 (#anonymous (0 arguments) function) STORE 1 STORE 2 LOAD_FAST_BY_INDEX 0 diff --git a/tests/unittests/resources/CompilerSuite/optimized_ir/builtins.expected b/tests/unittests/resources/CompilerSuite/optimized_ir/builtins.expected index 430019191..f5d2fd609 100644 --- a/tests/unittests/resources/CompilerSuite/optimized_ir/builtins.expected +++ b/tests/unittests/resources/CompilerSuite/optimized_ir/builtins.expected @@ -1,22 +1,22 @@ -page_#anonymous +global LOAD_CONST_STORE 0, 0 LOAD_CONST_STORE 1, 2 LOAD_CONST_STORE 2, 4 HALT 0 -page_1 (#anonymous) +page_1 (#anonymous (0 arguments) function) CALL_BUILTIN_WITHOUT_RETURN_ADDRESS 61, 1 .L0: RET 0 HALT 0 -page_2 (#anonymous) +page_2 (#anonymous (0 arguments) function) CALL_BUILTIN_WITHOUT_RETURN_ADDRESS 4, 2 .L1: RET 0 HALT 0 -page_3 (#anonymous) +page_3 (#anonymous (0 arguments) function) CALL_BUILTIN_WITHOUT_RETURN_ADDRESS 8, 3 .L2: RET 0 diff --git a/tests/unittests/resources/CompilerSuite/optimized_ir/call_builtin_not_optimized.expected b/tests/unittests/resources/CompilerSuite/optimized_ir/call_builtin_not_optimized.expected index 8dd4861af..41464e746 100644 --- a/tests/unittests/resources/CompilerSuite/optimized_ir/call_builtin_not_optimized.expected +++ b/tests/unittests/resources/CompilerSuite/optimized_ir/call_builtin_not_optimized.expected @@ -1,4 +1,4 @@ -page_#anonymous +global LOAD_CONST_STORE 0, 0 PUSH_RETURN_ADDRESS L1 LOAD_CONST 1 @@ -13,7 +13,7 @@ page_#anonymous POP 0 HALT 0 -page_1 (#anonymous) +page_1 (#anonymous (0 arguments) function) STORE 1 PUSH_RETURN_ADDRESS L0 LOAD_FAST_BY_INDEX 0 @@ -27,7 +27,7 @@ page_1 (#anonymous) RET 0 HALT 0 -page_2 (#anonymous) +page_2 (#anonymous (0 arguments) function) CALL_BUILTIN_WITHOUT_RETURN_ADDRESS 9, 1 .L2: RET 0 diff --git a/tests/unittests/resources/CompilerSuite/optimized_ir/closures.expected b/tests/unittests/resources/CompilerSuite/optimized_ir/closures.expected index 7d3d1878d..856d1b64f 100644 --- a/tests/unittests/resources/CompilerSuite/optimized_ir/closures.expected +++ b/tests/unittests/resources/CompilerSuite/optimized_ir/closures.expected @@ -1,4 +1,4 @@ -page_#anonymous +global LOAD_CONST_STORE 0, 0 PUSH_RETURN_ADDRESS L0 LOAD_CONST_LOAD_CONST 3, 4 @@ -51,7 +51,7 @@ page_#anonymous LOAD_CONST_STORE 18, 11 HALT 0 -page_1 (#anonymous) +page_1 (#anonymous (0 arguments) function) STORE 1 STORE 2 STORE 3 @@ -64,32 +64,32 @@ page_1 (#anonymous) RET 0 HALT 0 -page_2 (#anonymous) +page_2 (#anonymous (0 arguments) function) STORE 5 SET_VAL_FROM_INDEX 0, 2 LOAD_SYMBOL 2 RET 0 HALT 0 -page_3 (#anonymous) +page_3 (#anonymous (0 arguments) function) BUILTIN 2 RET 0 HALT 0 -page_4 (#anonymous) +page_4 (#anonymous (0 arguments) function) STORE 9 CAPTURE 9 MAKE_CLOSURE 15 RET 0 HALT 0 -page_5 (#anonymous) +page_5 (#anonymous (0 arguments) function) DECREMENT_STORE 9, 1 LOAD_FAST 9 RET 0 HALT 0 -page_6 (#anonymous) +page_6 (#anonymous (0 arguments) function) PUSH_RETURN_ADDRESS L8 LOAD_CONST 19 PUSH_RETURN_ADDRESS L9 diff --git a/tests/unittests/resources/CompilerSuite/optimized_ir/factorial.expected b/tests/unittests/resources/CompilerSuite/optimized_ir/factorial.expected index 6741dd2aa..bf024e745 100644 --- a/tests/unittests/resources/CompilerSuite/optimized_ir/factorial.expected +++ b/tests/unittests/resources/CompilerSuite/optimized_ir/factorial.expected @@ -1,4 +1,4 @@ -page_#anonymous +global LOAD_CONST_STORE 0, 0 PUSH_RETURN_ADDRESS L2 LOAD_CONST 3 @@ -11,7 +11,7 @@ page_#anonymous POP 0 HALT 0 -page_1 (#anonymous) +page_1 (#anonymous (0 arguments) function) STORE 1 LOAD_CONST_STORE 1, 2 LOAD_CONST_STORE 2, 3 diff --git a/tests/unittests/resources/CompilerSuite/optimized_ir/fused_math_ops.expected b/tests/unittests/resources/CompilerSuite/optimized_ir/fused_math_ops.expected index 53141d307..5ecd0bb72 100644 --- a/tests/unittests/resources/CompilerSuite/optimized_ir/fused_math_ops.expected +++ b/tests/unittests/resources/CompilerSuite/optimized_ir/fused_math_ops.expected @@ -1,4 +1,4 @@ -page_#anonymous +global LOAD_CONST_STORE 0, 0 LOAD_CONST_STORE 1, 1 LOAD_CONST_STORE 2, 2 diff --git a/tests/unittests/resources/CompilerSuite/optimized_ir/increments.expected b/tests/unittests/resources/CompilerSuite/optimized_ir/increments.expected index a94e4a8ff..1be56a212 100644 --- a/tests/unittests/resources/CompilerSuite/optimized_ir/increments.expected +++ b/tests/unittests/resources/CompilerSuite/optimized_ir/increments.expected @@ -1,4 +1,4 @@ -page_#anonymous +global LOAD_CONST_STORE 0, 0 INCREMENT_BY_INDEX 0, 4 SET_VAL 0 diff --git a/tests/unittests/resources/CompilerSuite/optimized_ir/jumps.expected b/tests/unittests/resources/CompilerSuite/optimized_ir/jumps.expected index c4c372456..edb1f543e 100644 --- a/tests/unittests/resources/CompilerSuite/optimized_ir/jumps.expected +++ b/tests/unittests/resources/CompilerSuite/optimized_ir/jumps.expected @@ -1,4 +1,4 @@ -page_#anonymous +global LOAD_CONST_STORE 0, 0 LOAD_CONST_STORE 1, 2 CREATE_SCOPE 0 @@ -116,7 +116,7 @@ page_#anonymous POP_SCOPE 0 HALT 0 -page_1 (#anonymous) +page_1 (#anonymous (0 arguments) function) STORE 1 LOAD_FAST_BY_INDEX 0 RET 0 diff --git a/tests/unittests/resources/CompilerSuite/optimized_ir/lists.expected b/tests/unittests/resources/CompilerSuite/optimized_ir/lists.expected index fcd9ad606..518667d57 100644 --- a/tests/unittests/resources/CompilerSuite/optimized_ir/lists.expected +++ b/tests/unittests/resources/CompilerSuite/optimized_ir/lists.expected @@ -1,4 +1,4 @@ -page_#anonymous +global LOAD_CONST_LOAD_CONST 0, 1 LOAD_CONST_LOAD_CONST 2, 3 STORE_LIST 4, 0 @@ -27,7 +27,7 @@ page_#anonymous APPEND_IN_PLACE_SYM_INDEX 1, 1 HALT 0 -page_1 (#anonymous) +page_1 (#anonymous (0 arguments) function) STORE_HEAD 0, 4 STORE_TAIL 0, 5 STORE_FROM 0, 6 diff --git a/tests/unittests/resources/CompilerSuite/optimized_ir/mul.expected b/tests/unittests/resources/CompilerSuite/optimized_ir/mul.expected index bca85ef78..60b4ba968 100644 --- a/tests/unittests/resources/CompilerSuite/optimized_ir/mul.expected +++ b/tests/unittests/resources/CompilerSuite/optimized_ir/mul.expected @@ -1,4 +1,4 @@ -page_#anonymous +global LOAD_CONST_STORE 0, 0 MUL_BY_INDEX 0, 2054 SET_VAL 0 @@ -11,7 +11,7 @@ page_#anonymous POP 0 HALT 0 -page_1 (#anonymous) +page_1 (#anonymous (0 arguments) function) MUL_BY 0, 2055 STORE 2 MUL_BY 0, 2055 diff --git a/tests/unittests/resources/CompilerSuite/optimized_ir/type.expected b/tests/unittests/resources/CompilerSuite/optimized_ir/type.expected index 721e7ab03..a3bde74c7 100644 --- a/tests/unittests/resources/CompilerSuite/optimized_ir/type.expected +++ b/tests/unittests/resources/CompilerSuite/optimized_ir/type.expected @@ -1,4 +1,4 @@ -page_#anonymous +global LOAD_CONST_STORE 0, 0 CHECK_TYPE_OF_BY_INDEX 0, 1 POP_JUMP_IF_TRUE L0 @@ -27,7 +27,7 @@ page_#anonymous POP 0 HALT 0 -page_1 (#anonymous) +page_1 (#anonymous (0 arguments) function) CHECK_TYPE_OF 0, 1 POP_JUMP_IF_TRUE L6 JUMP L7 From 747badb39da52d620d8e561204bdd671073321a4 Mon Sep 17 00:00:00 2001 From: Lexy Plt Date: Sun, 12 Jul 2026 15:22:41 +0200 Subject: [PATCH 04/26] feat(inliner): ARK-325, add a very basic IR inliner --- .run/a.ark.run.xml | 2 +- .../IntermediateRepresentation/Entity.hpp | 106 ++++++++++++- .../IntermediateRepresentation/IRInliner.hpp | 27 ++++ include/Ark/Compiler/Lowerer/ASTLowerer.hpp | 4 +- .../Ark/Compiler/Lowerer/LocalsLocator.hpp | 2 +- include/Ark/Compiler/ValTableElem.hpp | 2 +- .../IntermediateRepresentation/IRInliner.cpp | 145 +++++++++++++++++- .../IROptimizer.cpp | 10 +- .../Compiler/Lowerer/ASTLowerer.cpp | 8 +- src/arkreactor/VM/VM.cpp | 13 +- 10 files changed, 299 insertions(+), 20 deletions(-) diff --git a/.run/a.ark.run.xml b/.run/a.ark.run.xml index 0a85746a5..4ca054426 100644 --- a/.run/a.ark.run.xml +++ b/.run/a.ark.run.xml @@ -1,5 +1,5 @@ - + diff --git a/include/Ark/Compiler/IntermediateRepresentation/Entity.hpp b/include/Ark/Compiler/IntermediateRepresentation/Entity.hpp index 128196c1c..0722a2688 100644 --- a/include/Ark/Compiler/IntermediateRepresentation/Entity.hpp +++ b/include/Ark/Compiler/IntermediateRepresentation/Entity.hpp @@ -43,38 +43,133 @@ namespace Ark::internal::IR class Entity { public: + /** + * @brief Create a new IR Entity + * + * @param kind kind of entity (label, jump, instruction...) + */ explicit Entity(Kind kind); + /** + * @brief Create a new IR Entity + * + * @param inst instruction + * @param arg optional argument, default to 0 + */ explicit Entity(Instruction inst, uint16_t arg = 0); + /** + * @brief Create a new IR Entity + * + * @param inst instruction that takes two arguments + * @param primary_arg first argument on 12 bits + * @param secondary_arg second argument on 12 bits + */ Entity(Instruction inst, uint16_t primary_arg, uint16_t secondary_arg); + /** + * @brief Create a new IR Entity + * + * @param inst instruction that takes three arguments + * @param inst2 first argument on 8 bits + * @param inst3 second argument on 8 bits + * @param inst4 third argument on 8 bits + */ Entity(Instruction inst, uint8_t inst2, uint8_t inst3, uint8_t inst4); void replaceInstruction(Instruction replacement); + /** + * @brief Create a new Label IR Entity + * + * @param value value for the label + * @return Entity + */ static Entity Label(label_t value); + /** + * @brief Create a new Goto IR Entity + * + * @param label label the goto relates to + * @param inst jump instruction to use, default to JUMP + * @return Entity + */ static Entity Goto(const Entity& label, Instruction inst = Instruction::JUMP); + /** + * @brief Create a new Goto IR Entity + * + * @param label label the goto relates to + * @param inst jump instruction to use + * @param primary_arg argument for the jump instruction + * @return Entity + */ static Entity GotoWithArg(const Entity& label, Instruction inst, uint16_t primary_arg); + /** + * @brief Create a new Goto IR Entity + * + * @param label label the goto relates to + * @param cond true to use POP_JUMP_IF_TRUE, false to use POP_JUMP_IF_FALSE + * @return Entity + */ static Entity GotoIf(const Entity& label, bool cond); + /** + * @brief Return the bytecode representation of the IR Entity if it's an Opcode + * + * @return Word + */ [[nodiscard]] Word bytecode() const; + /** + * @brief Return the label of the IR Entity + * + * @return label_t + */ [[nodiscard]] label_t label() const { return m_label; } + /** + * @brief Return the kind of IR Entity + * @see Kind + * @return Kind + */ [[nodiscard]] Kind kind() const { return m_kind; } + /** + * @brief Return the underlying instruction of the IR Entity + * + * @return Instruction + */ [[nodiscard]] Instruction inst() const { return m_inst; } + /** + * @brief Return the primary argument of the IR Entity (can be 0 if the argument isn't used) + * @details The argument is on 16 bits for standard instructions ; for super instructions, only the first 12 bits are used + * @return uint16_t + */ [[nodiscard]] uint16_t primaryArg() const { return m_primary_arg; } + /** + * @brief Return the second argument of the IR Entity + * @details The argument is for super instructions, where only the first 12 bits are used + * @return uint16_t + */ [[nodiscard]] uint16_t secondaryArg() const { return m_secondary_arg; } + /** + * @brief Return the third argument of the IR Entity + * @details The argument is for special super instructions, where only the first 8 bits are used (for all arguments) + * @return uint16_t + */ [[nodiscard]] uint16_t tertiaryArg() const { return m_tertiary_arg; } + /** + * @brief Set the source location for an IR Entity, which is used to generate the file loc table + * + * @param filename + * @param line + */ void setSourceLocation(const std::string& filename, std::size_t line); void setOriginalSymbolId(std::optional id); @@ -85,6 +180,11 @@ namespace Ark::internal::IR [[nodiscard]] std::size_t sourceLine() const { return m_metadata.source_line; } + /** + * @brief Return the original symbol id an IR Entity refers to (only populated for LOAD_FAST_BY_INDEX, CALL_SYMBOL_BY_INDEX, and CALL_SYMBOL) + * + * @return std::optional + */ [[nodiscard]] std::optional originalSymbolId() const { return m_metadata.symbol_id; } private: @@ -100,10 +200,13 @@ namespace Ark::internal::IR { std::string source_file; std::size_t source_line { 0 }; - std::optional symbol_id; ///< Used for LOAD_SYMBOL_BY_INDEX to know the original symbol id and deoptimize when necessary + std::optional symbol_id; ///< Used by a few instructions to know the original symbol id and deoptimize when necessary } m_metadata; }; + /** + * @brief Block of IR entities, with attached metadata + */ struct Block { using vec_t = std::vector; @@ -112,6 +215,7 @@ namespace Ark::internal::IR { std::optional name; std::size_t argument_count { 0 }; + std::size_t addr { 0 }; bool is_closure { false }; bool is_recursive { false }; bool is_simple { false }; ///< Calls only builtin and operators, no user functions/C++ functions diff --git a/include/Ark/Compiler/IntermediateRepresentation/IRInliner.hpp b/include/Ark/Compiler/IntermediateRepresentation/IRInliner.hpp index d67a602b1..06e1e463c 100644 --- a/include/Ark/Compiler/IntermediateRepresentation/IRInliner.hpp +++ b/include/Ark/Compiler/IntermediateRepresentation/IRInliner.hpp @@ -16,8 +16,20 @@ #include #include +#include +#include +#include + namespace Ark::internal { + struct BlockInfo + { + long constant_id; + std::size_t addr; + std::string name; + std::optional symbol_id; + }; + class ARK_API IRInliner final : public Pass { public: @@ -48,6 +60,13 @@ namespace Ark::internal std::vector m_ir; std::vector m_symbols; std::vector m_values; + std::vector m_funcs; + + enum class CallKind + { + Symbol, + Constant + }; /** * @brief Check if a block can be inlined in another one @@ -57,6 +76,14 @@ namespace Ark::internal * @return true if the candidate can be inlined */ [[nodiscard]] static bool canBeInlined(const IR::Block& candidate, const IR::Block& source) noexcept; + + [[nodiscard]] std::optional blockToInlineInCall(CallKind kind, const std::vector& pages, std::optional maybe_id, const IR::Block& current) const noexcept; + + void inlineBlock(const IR::Block& inlinee, IR::Block& destination); + + void extractPagesMetadata(const std::vector& pages); + + [[nodiscard]] std::optional findBlockBy(CallKind kind, uint16_t id) const noexcept; }; } diff --git a/include/Ark/Compiler/Lowerer/ASTLowerer.hpp b/include/Ark/Compiler/Lowerer/ASTLowerer.hpp index 766fb295b..eeb7aa140 100644 --- a/include/Ark/Compiler/Lowerer/ASTLowerer.hpp +++ b/include/Ark/Compiler/Lowerer/ASTLowerer.hpp @@ -134,13 +134,15 @@ namespace Ark::internal { if (!args.temp) { + const std::size_t new_page_addr = m_start_page_at_offset + m_code_pages.size(); m_code_pages.emplace_back( IR::Block::Metadata { .name = args.name, .argument_count = 0, + .addr = new_page_addr, .is_closure = args.closure }, IR::Block::vec_t {}); - return Page { .index = m_start_page_at_offset + m_code_pages.size() - 1u, .is_temp = false }; + return Page { .index = new_page_addr, .is_temp = false }; } m_temp_pages.emplace_back(); diff --git a/include/Ark/Compiler/Lowerer/LocalsLocator.hpp b/include/Ark/Compiler/Lowerer/LocalsLocator.hpp index 6972bd9de..c294795b9 100644 --- a/include/Ark/Compiler/Lowerer/LocalsLocator.hpp +++ b/include/Ark/Compiler/Lowerer/LocalsLocator.hpp @@ -70,7 +70,7 @@ namespace Ark::internal * @return true if vars were dropped, meaning that we created at least one variable in a branch * @return false if nothing was dropped */ - bool dropVarsForBranch(); + [[nodiscard]] bool dropVarsForBranch(); /** * @brief Mark the last variable of a scope as unreachable, blocking lookupLastScopeByName(..) from returning a value past that point diff --git a/include/Ark/Compiler/ValTableElem.hpp b/include/Ark/Compiler/ValTableElem.hpp index 53f78c849..3851c35e4 100644 --- a/include/Ark/Compiler/ValTableElem.hpp +++ b/include/Ark/Compiler/ValTableElem.hpp @@ -19,7 +19,7 @@ namespace Ark::internal { /** - * @brief Enumeration to keep track of the type of a Compiler Value + * @brief Enumeration to keep track of the type of Compiler Value * */ enum class ValTableElemType diff --git a/src/arkreactor/Compiler/IntermediateRepresentation/IRInliner.cpp b/src/arkreactor/Compiler/IntermediateRepresentation/IRInliner.cpp index 3fa3f696c..688d2a243 100644 --- a/src/arkreactor/Compiler/IntermediateRepresentation/IRInliner.cpp +++ b/src/arkreactor/Compiler/IntermediateRepresentation/IRInliner.cpp @@ -1,5 +1,8 @@ #include +#include +#include + namespace Ark::internal { IRInliner::IRInliner(const unsigned debug) : @@ -12,8 +15,62 @@ namespace Ark::internal m_symbols = symbols; m_values = values; - // TODO - m_ir = pages; + extractPagesMetadata(pages); + + // TODO: we'll need to move some page index if a page is removed! + // TODO: some pages could be removed if they are inlined everywhere! + // TODO: should we start from the end to inline as much as we can? or do we want to inline the user code first, then the stdlib? + for (const auto& block : pages) + { + IR::Block new_block { + .metadata = { + .name = block.metadata.name, + .argument_count = block.metadata.argument_count, + .addr = block.metadata.addr, + .is_closure = block.metadata.is_closure, + .is_recursive = block.metadata.is_recursive, + .is_simple = block.metadata.is_simple }, + .data = {} + }; + + // We only have to deal with CALL_SYMBOL, CALL_SYMBOL_BY_INDEX, which deal with symbols, + // and CALL which can deal with constant ids (eg `((fun (a) (print a)) 5)`) + for (const auto& entity : block.data) + { + std::optional maybe_id; + CallKind kind = CallKind::Symbol; + + if (entity.inst() == CALL_SYMBOL || entity.inst() == CALL_SYMBOL_BY_INDEX) + maybe_id = entity.originalSymbolId(); + else if (entity.inst() == CALL) + { + // TODO: find the function being called, check if it's a constant, then if we know it + maybe_id = {}; + kind = CallKind::Constant; + } + + if (const auto maybe_block = blockToInlineInCall(kind, pages, maybe_id, block); maybe_block.has_value()) + { + const IR::Block& inlinee = pages[maybe_block->addr]; + + if (new_block.data[new_block.data.size() - 1 - inlinee.metadata.argument_count].inst() == PUSH_RETURN_ADDRESS) + new_block.data.erase(new_block.data.end() - static_cast(1 + inlinee.metadata.argument_count)); + +// new_block.data.emplace_back(CREATE_SCOPE); + inlineBlock(inlinee, new_block); +// new_block.data.emplace_back(POP_SCOPE); + } + // TODO: find a better way to not fuck up/repair the indices + else if (entity.inst() == LOAD_FAST_BY_INDEX) + new_block.data.emplace_back(LOAD_FAST, entity.originalSymbolId().value()); + else if (entity.inst() == CALL_SYMBOL_BY_INDEX) + new_block.data.emplace_back(CALL_SYMBOL, entity.originalSymbolId().value(), entity.secondaryArg()); + else + new_block.data.emplace_back(entity); + } + + m_ir.emplace_back(new_block); + } m_logger.traceEnd(); } @@ -36,4 +93,88 @@ namespace Ark::internal // TODO: create a proper constant and make this less arbitrary? return candidate.metadata.is_simple && candidate_inst_count < 24; } + + std::optional IRInliner::blockToInlineInCall(const CallKind kind, const std::vector& pages, const std::optional maybe_id, const IR::Block& current) const noexcept + { + if (!maybe_id.has_value()) + return std::nullopt; + + const uint16_t id = maybe_id.value(); + std::optional maybe_block = findBlockBy(kind, id); + if (!maybe_block.has_value()) + return std::nullopt; + + const std::size_t block_addr = maybe_block->addr; + if (canBeInlined(pages[block_addr], current)) + return maybe_block; + return std::nullopt; + } + + void IRInliner::inlineBlock(const IR::Block& inlinee, IR::Block& destination) + { + m_logger.debug("Inlining call to {} inside {} @ {}", inlinee.debugName(), destination.debugName(), destination.metadata.addr); + + // TODO: do a better inlining job + // this can currently fuck up symbol indices (LOAD_FAST_BY_ID), overwrite variables from the parents, and maybe more + for (const IR::Entity& entity : inlinee.data) + { + if (entity.inst() == RET) + break; + + if (entity.inst() == LOAD_FAST_BY_INDEX) + destination.data.emplace_back(LOAD_FAST, entity.originalSymbolId().value()); + else if (entity.inst() == CALL_SYMBOL_BY_INDEX) + destination.data.emplace_back(CALL_SYMBOL, entity.originalSymbolId().value(), entity.secondaryArg()); + else + destination.data.emplace_back(entity); + } + } + + void IRInliner::extractPagesMetadata(const std::vector& pages) + { + for (std::size_t i = 0, end = pages.size(); i < end; ++i) + { + const std::string& name = pages[i].debugName(); + if (name != IR::AnonymousBlockName) + { + const auto it_val = std::ranges::find_if(m_values, [i](const ValTableElem& elem) -> bool { + return elem.type == ValTableElemType::PageAddr && std::get(elem.value) == i; + }); + assert(it_val != m_values.end() && "Could not find a constant referencing the current page!"); + + const auto it_sym = std::ranges::find_if(m_symbols, [&name](const std::string& sym) -> bool { + return name == sym; + }); + + m_funcs.emplace_back(BlockInfo { + .constant_id = static_cast(std::distance(m_values.begin(), it_val)), + .addr = i, + .name = pages[i].debugName(), + .symbol_id = it_sym == m_symbols.end() + ? std::nullopt + : std::make_optional(std::distance(m_symbols.begin(), it_sym)) }); + } + } + } + + std::optional IRInliner::findBlockBy(const CallKind kind, const uint16_t id) const noexcept + { + const auto it = std::ranges::find_if( + m_funcs, + [id, kind](const BlockInfo& info) -> bool { + switch (kind) + { + case CallKind::Symbol: + return info.symbol_id.has_value() && std::cmp_equal(info.symbol_id.value(), id); + + case CallKind::Constant: + return std::cmp_equal(info.constant_id, id); + } + return false; + }); + + if (it != m_funcs.end()) + return *it; + return std::nullopt; + } } diff --git a/src/arkreactor/Compiler/IntermediateRepresentation/IROptimizer.cpp b/src/arkreactor/Compiler/IntermediateRepresentation/IROptimizer.cpp index 6c8d4aa35..bd06a7ecb 100644 --- a/src/arkreactor/Compiler/IntermediateRepresentation/IROptimizer.cpp +++ b/src/arkreactor/Compiler/IntermediateRepresentation/IROptimizer.cpp @@ -22,6 +22,9 @@ namespace Ark::internal IROptimizer::IROptimizer(const unsigned debug) : Pass("IROptimizer", debug) { + // TODO: we could add rules to optimize ( ) to have a precomputed value instead + // TODO: same for ( ) + // TODO: optimize for LOAD x, STORE a, LOAD a? m_ruleset = { Rule { { LOAD_CONST, LOAD_CONST }, LOAD_CONST_LOAD_CONST }, Rule { { LOAD_CONST, STORE }, LOAD_CONST_STORE }, @@ -247,13 +250,8 @@ namespace Ark::internal { for (const auto& three : math_ops) m_ruleset.emplace_back(Rule { { one, two, three }, fuseMathOps3 }); - } - } - - for (const auto& one : math_ops) - { - for (const auto& two : math_ops) m_ruleset.emplace_back(Rule { { one, two }, fuseMathOps2 }); + } } m_logger.debug("Loaded {} rules", m_ruleset.size()); diff --git a/src/arkreactor/Compiler/Lowerer/ASTLowerer.cpp b/src/arkreactor/Compiler/Lowerer/ASTLowerer.cpp index ac395b680..6c9d52770 100644 --- a/src/arkreactor/Compiler/Lowerer/ASTLowerer.cpp +++ b/src/arkreactor/Compiler/Lowerer/ASTLowerer.cpp @@ -588,13 +588,13 @@ namespace Ark::internal if (x.isAnonymousFunction()) m_opened_vars.pop(); + // needed for the IRInliner ; the scope has to be dropped AFTER we set the metadata, as we need it + setFunctionMetadata(function_body_page, arg_count); + // return last value on the stack page(function_body_page).emplace_back(RET); m_locals_locator.deleteScope(); - // needed for the IRInliner - setFunctionMetadata(function_body_page, arg_count); - // if the computed function is unused, pop it if (is_result_unused) { @@ -1001,7 +1001,7 @@ namespace Ark::internal case CallType::Symbol: assert(call_arg.has_value() && "Expected a value for call_arg with CallType::Symbol"); - page(p).emplace_back(CALL_SYMBOL, call_arg.value(), args_count); + page(p).emplace_back(CALL_SYMBOL, call_arg.value(), args_count).setOriginalSymbolId(call_arg.value()); break; case CallType::SymbolByIndex: diff --git a/src/arkreactor/VM/VM.cpp b/src/arkreactor/VM/VM.cpp index 8407b61d2..85e4e5c9b 100644 --- a/src/arkreactor/VM/VM.cpp +++ b/src/arkreactor/VM/VM.cpp @@ -397,16 +397,21 @@ namespace Ark return m_exit_code; } - int VM::safeRun(ExecutionContext& context, const std::size_t untilFrameCount, const bool fail_with_exception) + int VM::safeRun(ExecutionContext& context, const std::size_t untilFrameCount, const bool fail_with_exception [[maybe_unused]]) { m_running = true; +#define WITH_TRY + +#ifdef WITH_TRY try { +#endif if (m_state.m_features & FeatureVMDebugger) unsafeRun(context, untilFrameCount); else unsafeRun(context, untilFrameCount); +#ifdef WITH_TRY } catch (const Error& e) { @@ -435,13 +440,15 @@ namespace Ark if (fail_with_exception) throw; -#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION +# ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION throw; -#endif +# endif fmt::println("Unknown error"); backtrace(context); m_exit_code = 1; } +#endif +#undef WITH_TRY return m_exit_code; } From 04ec4ac11242a1f2599e74811ca4281dd9b1fe67 Mon Sep 17 00:00:00 2001 From: Lexy Plt Date: Sat, 18 Jul 2026 19:20:50 +0200 Subject: [PATCH 05/26] feat(debugger): add new 'scopes' command, and avoid tracing instructions for code comming from the debugger --- .../IntermediateRepresentation/Entity.hpp | 14 ++++ include/Ark/VM/Debugger.hpp | 3 + .../IntermediateRepresentation/IRCompiler.cpp | 14 +--- src/arkreactor/VM/Debugger.cpp | 78 +++++++++++++++---- 4 files changed, 82 insertions(+), 27 deletions(-) diff --git a/include/Ark/Compiler/IntermediateRepresentation/Entity.hpp b/include/Ark/Compiler/IntermediateRepresentation/Entity.hpp index 0722a2688..87a0ad5bb 100644 --- a/include/Ark/Compiler/IntermediateRepresentation/Entity.hpp +++ b/include/Ark/Compiler/IntermediateRepresentation/Entity.hpp @@ -227,6 +227,20 @@ namespace Ark::internal::IR return metadata.name.value_or(AnonymousBlockName); } + [[nodiscard]] std::string metadataRepr() const + { + std::string flags; + if (metadata.is_recursive) + flags += "recursive"; + if (metadata.is_simple) + flags += std::string(flags.empty() ? "" : " ") + "simple"; + if (metadata.is_closure) + flags += std::string(flags.empty() ? "" : " ") + "closure"; + else + flags += std::string(flags.empty() ? "" : " ") + "function"; + return flags; + } + [[nodiscard]] std::size_t instructionCount() const { const auto length = std::ranges::count_if(data, [](const auto& a) { diff --git a/include/Ark/VM/Debugger.hpp b/include/Ark/VM/Debugger.hpp index 0515675c5..fd75e0530 100644 --- a/include/Ark/VM/Debugger.hpp +++ b/include/Ark/VM/Debugger.hpp @@ -172,8 +172,11 @@ namespace Ark::internal void showContext(const VM& vm, const ExecutionContext& context) const; void showStack(VM& vm, const ExecutionContext& context, std::size_t count) const; void showLocals(VM& vm, ExecutionContext& context, std::size_t count) const; + void showScopes(VM& vm, ExecutionContext& context, std::size_t count) const; void showPreviousInstructions(const VM& vm, std::size_t count) const; + void showLocals(const ScopeView& scope, VM& vm, std::optional limit = std::nullopt) const; + std::optional prompt(std::size_t ip, std::size_t pp, VM& vm, ExecutionContext& context); /** diff --git a/src/arkreactor/Compiler/IntermediateRepresentation/IRCompiler.cpp b/src/arkreactor/Compiler/IntermediateRepresentation/IRCompiler.cpp index 74f52fae6..c9ec58421 100644 --- a/src/arkreactor/Compiler/IntermediateRepresentation/IRCompiler.cpp +++ b/src/arkreactor/Compiler/IntermediateRepresentation/IRCompiler.cpp @@ -75,24 +75,14 @@ namespace Ark::internal fmt::println(stream, "global"); else { - std::string flags; - if (block.metadata.is_recursive) - flags += " recursive"; - if (block.metadata.is_simple) - flags += " simple"; - if (block.metadata.is_closure) - flags += " closure"; - else - flags += " function"; - fmt::println( stream, - "page_{} ({} ({} argument{}){})", + "page_{} ({} ({} argument{}) {})", index, block.debugName(), block.metadata.argument_count, block.metadata.argument_count == 1 ? "" : "s", - flags); + block.metadataRepr()); } for (const auto& entity : block.data) diff --git a/src/arkreactor/VM/Debugger.cpp b/src/arkreactor/VM/Debugger.cpp index 17d6f5d9f..b82bb3dff 100644 --- a/src/arkreactor/VM/Debugger.cpp +++ b/src/arkreactor/VM/Debugger.cpp @@ -134,7 +134,9 @@ namespace Ark::internal void Debugger::registerInstruction(const uint32_t word) noexcept { - m_previous_insts.push_back(word); + // We don't want to register instructions from code entered in the debugger! + if (!m_running) + m_previous_insts.push_back(word); } std::optional Debugger::Command::getArgs(const std::string& line, std::ostream& os) const @@ -231,6 +233,15 @@ namespace Ark::internal showLocals(*args.vm_ptr, *args.ctx_ptr, arg.value()); return false; }), + Command( + StartsWith("scopes"), + { { "n", "5" } }, + "show the last n scopes", + [this](const std::string& line, const CommandArgs& args) { + if (const auto arg = args.me.argAsCount(line, 0, m_os)) + showScopes(*args.vm_ptr, *args.ctx_ptr, arg.value()); + return false; + }), Command( "ptr", "show the values of the VM pointers", @@ -330,34 +341,71 @@ namespace Ark::internal if (limit > 0 && count > 0) { fmt::println(m_os, "scope size: {}", limit); - fmt::println(m_os, "index | id | name | type | value"); + showLocals(context.locals[context.locals.size() - 2], vm, count); + } + else + fmt::println(m_os, "Current scope is empty"); + + fmt::println(m_os, ""); + } + + void Debugger::showScopes(VM& vm, ExecutionContext& context, const std::size_t count) const + { + if (count == 0) + fmt::println(m_os, "Nothing to show, count must be > 0"); + else + { + const std::size_t scopes_count = context.locals.size() - 1; + fmt::println(m_os, "There are {} scope{}\n", scopes_count, scopes_count == 1 ? "" : "s"); + std::size_t i = 0; do { - if (limit <= i) + if (scopes_count <= i) break; - auto& [id, value] = context.locals[context.locals.size() - 2].atPosReverse(i); - const auto color = m_colorize ? fmt::fg(i % 2 == 0 ? fmt::color::forest_green : fmt::color::cornflower_blue) : fmt::text_style(); + // `i` is in [0, scopes_count[, so scopes_count - max(i) == 0, we can safely subtract 1 + const std::size_t idx = scopes_count - i - 1; + const auto& scope = context.locals[idx]; + + fmt::println(m_os, "Scope {}, size: {}", idx, scope.size()); + if (scope.size() > 0) + showLocals(scope, vm); + fmt::println(""); - fmt::println( - m_os, - "{:>5} | {:3} | {:14} | {:>9} | {}", - fmt::styled(limit - i - 1, color), - fmt::styled(id, color), - fmt::styled(vm.m_state.m_symbols[id], color), - fmt::styled(std::to_string(value.valueType()), color), - fmt::styled(value.toString(vm, /* show_as_code= */ true), color)); ++i; } while (i < count); } - else - fmt::println(m_os, "Current scope is empty"); fmt::println(m_os, ""); } + void Debugger::showLocals(const ScopeView& scope, VM& vm, std::optional limit) const + { + fmt::println(m_os, "index | id | name | type | value"); + std::size_t i = 0; + + do + { + if (scope.size() <= i) + break; + + auto& [id, value] = scope.atPosReverse(i); + const auto color = m_colorize ? fmt::fg(i % 2 == 0 ? fmt::color::forest_green : fmt::color::cornflower_blue) : fmt::text_style(); + + fmt::println( + m_os, + "{:>5} | {:3} | {:14} | {:>9} | {}", + fmt::styled(scope.size() - i - 1, color), + fmt::styled(id, color), + fmt::styled(vm.m_state.m_symbols[id], color), + fmt::styled(std::to_string(value.valueType()), color), + fmt::styled(value.toString(vm, /* show_as_code= */ true), color)); + ++i; + } while (!limit.has_value() || i < limit.value()); + } + void Debugger::showPreviousInstructions(const VM& vm, const std::size_t count) const { BytecodeReader bcr; From f8147e3d013a5874a92bf9baf471a86d9e9d6acb Mon Sep 17 00:00:00 2001 From: Lexy Plt Date: Sat, 18 Jul 2026 23:27:02 +0200 Subject: [PATCH 06/26] feat(compiler): ARK-325, generate new labels when inlining code --- .run/a.ark.run.xml | 2 +- .../IntermediateRepresentation/Entity.hpp | 29 +++++++++ .../IntermediateRepresentation/IRInliner.hpp | 4 +- include/Ark/Compiler/Lowerer/ASTLowerer.hpp | 5 ++ .../IntermediateRepresentation/Entity.cpp | 5 ++ .../IntermediateRepresentation/IRInliner.cpp | 63 ++++++++++++++----- 6 files changed, 92 insertions(+), 16 deletions(-) diff --git a/.run/a.ark.run.xml b/.run/a.ark.run.xml index 4ca054426..88254d1d9 100644 --- a/.run/a.ark.run.xml +++ b/.run/a.ark.run.xml @@ -7,4 +7,4 @@ - + \ No newline at end of file diff --git a/include/Ark/Compiler/IntermediateRepresentation/Entity.hpp b/include/Ark/Compiler/IntermediateRepresentation/Entity.hpp index 87a0ad5bb..687614962 100644 --- a/include/Ark/Compiler/IntermediateRepresentation/Entity.hpp +++ b/include/Ark/Compiler/IntermediateRepresentation/Entity.hpp @@ -79,6 +79,8 @@ namespace Ark::internal::IR void replaceInstruction(Instruction replacement); + void replaceLabel(label_t replacement); + /** * @brief Create a new Label IR Entity * @@ -122,6 +124,33 @@ namespace Ark::internal::IR */ [[nodiscard]] Word bytecode() const; + /** + * @brief Check if the Entity has a label attached + * + * @return bool + */ + [[nodiscard]] bool hasLabel() const + { + switch (m_kind) + { + case Kind::Label: + [[fallthrough]]; + case Kind::Goto: + [[fallthrough]]; + case Kind::GotoWithArg: + return true; + + case Kind::Opcode: + [[fallthrough]]; + case Kind::Opcode2Args: + [[fallthrough]]; + case Kind::Opcode3Args: + return false; + } + + return false; + } + /** * @brief Return the label of the IR Entity * diff --git a/include/Ark/Compiler/IntermediateRepresentation/IRInliner.hpp b/include/Ark/Compiler/IntermediateRepresentation/IRInliner.hpp index 06e1e463c..89ce49d4c 100644 --- a/include/Ark/Compiler/IntermediateRepresentation/IRInliner.hpp +++ b/include/Ark/Compiler/IntermediateRepresentation/IRInliner.hpp @@ -46,8 +46,9 @@ namespace Ark::internal * @param pages list of lists of IR entities generated by the compiler * @param symbols symbol table generated by the compiler * @param values value table generated by the compiler + * @param last_label last label generated by the AST lowerer */ - void process(const std::vector& pages, const std::vector& symbols, const std::vector& values); + void process(const std::vector& pages, const std::vector& symbols, const std::vector& values, IR::label_t last_label); /** * @brief Return the IR blocks (one per scope) @@ -61,6 +62,7 @@ namespace Ark::internal std::vector m_symbols; std::vector m_values; std::vector m_funcs; + IR::label_t m_current_label; enum class CallKind { diff --git a/include/Ark/Compiler/Lowerer/ASTLowerer.hpp b/include/Ark/Compiler/Lowerer/ASTLowerer.hpp index eeb7aa140..c1cc17d17 100644 --- a/include/Ark/Compiler/Lowerer/ASTLowerer.hpp +++ b/include/Ark/Compiler/Lowerer/ASTLowerer.hpp @@ -98,6 +98,11 @@ namespace Ark::internal */ [[nodiscard]] const std::vector& values() const noexcept; + [[nodiscard]] IR::label_t lastLabel() const noexcept + { + return m_current_label; + } + private: struct Page { diff --git a/src/arkreactor/Compiler/IntermediateRepresentation/Entity.cpp b/src/arkreactor/Compiler/IntermediateRepresentation/Entity.cpp index b44723bc8..5258cb48a 100644 --- a/src/arkreactor/Compiler/IntermediateRepresentation/Entity.cpp +++ b/src/arkreactor/Compiler/IntermediateRepresentation/Entity.cpp @@ -27,6 +27,11 @@ namespace Ark::internal::IR m_inst = replacement; } + void Entity::replaceLabel(const label_t replacement) + { + m_label = replacement; + } + Entity Entity::Label(const label_t value) { auto entity = Entity(Kind::Label); diff --git a/src/arkreactor/Compiler/IntermediateRepresentation/IRInliner.cpp b/src/arkreactor/Compiler/IntermediateRepresentation/IRInliner.cpp index 688d2a243..91e0b15ba 100644 --- a/src/arkreactor/Compiler/IntermediateRepresentation/IRInliner.cpp +++ b/src/arkreactor/Compiler/IntermediateRepresentation/IRInliner.cpp @@ -9,11 +9,12 @@ namespace Ark::internal Pass("IRInliner", debug) {} - void IRInliner::process(const std::vector& pages, const std::vector& symbols, const std::vector& values) + void IRInliner::process(const std::vector& pages, const std::vector& symbols, const std::vector& values, const IR::label_t last_label) { m_logger.traceStart("process"); m_symbols = symbols; m_values = values; + m_current_label = last_label + 1; extractPagesMetadata(pages); @@ -35,8 +36,10 @@ namespace Ark::internal // We only have to deal with CALL_SYMBOL, CALL_SYMBOL_BY_INDEX, which deal with symbols, // and CALL which can deal with constant ids (eg `((fun (a) (print a)) 5)`) - for (const auto& entity : block.data) + for (std::size_t i = 0, end = block.data.size(); i < end; ++i) { + const auto& entity = block.data[i]; + std::optional maybe_id; CallKind kind = CallKind::Symbol; @@ -53,18 +56,22 @@ namespace Ark::internal { const IR::Block& inlinee = pages[maybe_block->addr]; - if (new_block.data[new_block.data.size() - 1 - inlinee.metadata.argument_count].inst() == PUSH_RETURN_ADDRESS) - new_block.data.erase(new_block.data.end() - static_cast(1 + inlinee.metadata.argument_count)); + // retrieve the return label of the call instruction, to know which PUSH_RETURN_ADDRESS instruction we'll have to remove + if (i + 1 < end) + { + assert(block.data[i + 1].kind() == IR::Kind::Label && "Expected a label right after the CALL instruction! The AST lowerer messed up somewhere"); + + const IR::label_t return_label = block.data[i + 1].label(); + const std::size_t removed = std::erase_if(new_block.data, [return_label](const IR::Entity& e) -> bool { + return e.kind() == IR::Kind::Goto && e.inst() == PUSH_RETURN_ADDRESS && e.label() == return_label; + }); + + if (removed == 0) + throw std::runtime_error(fmt::format("No PUSH_RETURN_ADDRESS L{} instruction removed, even though one was expected", return_label)); + } -// new_block.data.emplace_back(CREATE_SCOPE); inlineBlock(inlinee, new_block); -// new_block.data.emplace_back(POP_SCOPE); } - // TODO: find a better way to not fuck up/repair the indices - else if (entity.inst() == LOAD_FAST_BY_INDEX) - new_block.data.emplace_back(LOAD_FAST, entity.originalSymbolId().value()); - else if (entity.inst() == CALL_SYMBOL_BY_INDEX) - new_block.data.emplace_back(CALL_SYMBOL, entity.originalSymbolId().value(), entity.secondaryArg()); else new_block.data.emplace_back(entity); } @@ -112,22 +119,50 @@ namespace Ark::internal void IRInliner::inlineBlock(const IR::Block& inlinee, IR::Block& destination) { - m_logger.debug("Inlining call to {} inside {} @ {}", inlinee.debugName(), destination.debugName(), destination.metadata.addr); + if (destination.metadata.addr == 0) + m_logger.debug("Inlining call to '{}' ({}) inside global scope", inlinee.debugName(), inlinee.metadataRepr()); + else + m_logger.debug("Inlining call to '{}' ({}) inside '{}' @ {}", inlinee.debugName(), inlinee.metadataRepr(), destination.debugName(), destination.metadata.addr); // TODO: do a better inlining job - // this can currently fuck up symbol indices (LOAD_FAST_BY_ID), overwrite variables from the parents, and maybe more + // this can currently fuck up symbol indices (LOAD_FAST_BY_ID), overwrite variables from the parents, and maybe more -> fixed if we put a scope around + destination.data.emplace_back(CREATE_SCOPE); + + // We need to create new, unique labels for the inlined code. + // When we meet a label, we'll register it, and replace it with a new label + // in the inlined code. That way, if we find it again later in the code, + // we can use the correct value. + std::unordered_map old_to_new_label; + + // TODO: special inlining for call_builtin (when we only do that in a function, like the iroptimizer does with call builtin without ret addr) + // TODO: decide if we want to keep create_scope, (inlinee), pop_scope for (const IR::Entity& entity : inlinee.data) { if (entity.inst() == RET) break; - if (entity.inst() == LOAD_FAST_BY_INDEX) + if (entity.hasLabel()) + { + IR::Entity labelled_entity = entity; + if (auto it = old_to_new_label.find(entity.label()); it != old_to_new_label.end()) + labelled_entity.replaceLabel(it->second); + else + { + labelled_entity.replaceLabel(m_current_label); + old_to_new_label[entity.label()] = m_current_label++; + } + + destination.data.emplace_back(labelled_entity); + } + else if (entity.inst() == LOAD_FAST_BY_INDEX) destination.data.emplace_back(LOAD_FAST, entity.originalSymbolId().value()); else if (entity.inst() == CALL_SYMBOL_BY_INDEX) destination.data.emplace_back(CALL_SYMBOL, entity.originalSymbolId().value(), entity.secondaryArg()); else destination.data.emplace_back(entity); } + + destination.data.emplace_back(POP_SCOPE); } void IRInliner::extractPagesMetadata(const std::vector& pages) From 6a4361ca21cf4b77e27ddcfdc49560375c78d67f Mon Sep 17 00:00:00 2001 From: Lexy Plt Date: Sat, 18 Jul 2026 23:29:05 +0200 Subject: [PATCH 07/26] feat(compiler): ARK-325, do not inline functions mutating their arguments --- .run/a.ark.run.xml | 2 +- .../Ark/Compiler/IntermediateRepresentation/Entity.hpp | 4 ++++ include/Ark/Compiler/Lowerer/ASTLowerer.hpp | 2 +- lib/std | 2 +- .../Compiler/IntermediateRepresentation/IRInliner.cpp | 4 +++- src/arkreactor/Compiler/Lowerer/ASTLowerer.cpp | 8 ++++++-- 6 files changed, 16 insertions(+), 6 deletions(-) diff --git a/.run/a.ark.run.xml b/.run/a.ark.run.xml index 88254d1d9..4ca054426 100644 --- a/.run/a.ark.run.xml +++ b/.run/a.ark.run.xml @@ -7,4 +7,4 @@