Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
390a194
feat(compiler): ARK-325, track the origin of an ir block
SuperFola Jul 11, 2026
2505670
feat(compiler): ARK-325, add an empty ir inliner in between the ast l…
SuperFola Jul 11, 2026
58848ef
feat(compiler): ARK-325, add more metadata to IR blocks, and create a…
SuperFola Jul 12, 2026
747badb
feat(inliner): ARK-325, add a very basic IR inliner
SuperFola Jul 12, 2026
04ec4ac
feat(debugger): add new 'scopes' command, and avoid tracing instructi…
SuperFola Jul 18, 2026
f8147e3
feat(compiler): ARK-325, generate new labels when inlining code
SuperFola Jul 18, 2026
6a4361c
feat(compiler): ARK-325, do not inline functions mutating their argum…
SuperFola Jul 18, 2026
6d02797
feat(compiler): ARK-325, inline builtin proxies
SuperFola Jul 19, 2026
520c128
feat(compiler, vm): ARK-325, instruction POP_SCOPE now takes a mode, …
SuperFola Jul 19, 2026
ce97da8
fix(ast lowerer): check for breakpoints at the end of begin nodes to …
SuperFola Jul 19, 2026
6ecff4d
feat(config): add a feature toggle for the ir inliner, distinct from …
SuperFola Jul 19, 2026
2349f2a
chore: update tests
SuperFola Jul 19, 2026
da394f8
feat(tests): duplicate lang suite to run with and without the ir inliner
SuperFola Jul 19, 2026
e789872
feat(cli): new toggle -f(no-)irinliner
SuperFola Jul 19, 2026
add7d89
fix: add missing header in Entity.hpp
SuperFola Jul 19, 2026
9739797
fix: use a constexpr string_view instead of a constexpr string, other…
SuperFola Jul 19, 2026
bd1372a
feat(compiler): ARK-325, inline only when argument count matches
SuperFola Jul 19, 2026
48b3a44
chore: ARK-325, add tests for inlined IR
SuperFola Jul 19, 2026
6968e32
fix(compiler): do not try to optimise IR that uses labels that might …
SuperFola Jul 20, 2026
2dbd416
feat(compiler): ARK-325, inline functions that have been declared onl…
SuperFola Jul 20, 2026
db68c2d
feat(compiler): add the number of instructions in a page in the textu…
SuperFola Jul 20, 2026
e9c2f6f
fix(compiler): set the correct source location when inlining code
SuperFola Jul 20, 2026
59bf16d
test: ARK-374, add empty escape sequence test
SuperFola Jul 20, 2026
a4440a1
test: add a test for the new debugger command 'scopes'
SuperFola Jul 20, 2026
ffd6407
test: add specific IR inlining test where inlining shouldn't happen
SuperFola Jul 20, 2026
4274418
fix(compiler): keep provenance information of IR blocks inside the IR…
SuperFola Jul 21, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ include/Ark/Constants.hpp
.cache/
build/
build_emscripten/
cache-cppcheck/
ninja/
cmake-build-*/
out/
Expand Down
5 changes: 3 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
2 changes: 1 addition & 1 deletion .run/a.ark.run.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="a.ark" type="CMakeRunConfiguration" factoryName="Application" folderName="arkscript" PROGRAM_PARAMS="-fdump-ir a.ark" REDIRECT_INPUT="false" ELEVATE="false" USE_EXTERNAL_CONSOLE="false" EMULATE_TERMINAL="false" WORKING_DIR="file://$PROJECT_DIR$" PASS_PARENT_ENVS_2="true" PROJECT_NAME="ark" TARGET_NAME="arkscript" CONFIG_NAME="Debug" RUN_TARGET_PROJECT_NAME="ark" RUN_TARGET_NAME="arkscript">
<configuration default="false" name="a.ark" type="CMakeRunConfiguration" factoryName="Application" folderName="arkscript" PROGRAM_PARAMS="-fdump-ir -ddd a.ark" REDIRECT_INPUT="false" ELEVATE="false" USE_EXTERNAL_CONSOLE="false" EMULATE_TERMINAL="false" WORKING_DIR="file://$PROJECT_DIR$" PASS_PARENT_ENVS_2="true" PROJECT_NAME="ark" TARGET_NAME="arkscript" CONFIG_NAME="Debug" RUN_TARGET_PROJECT_NAME="ark" RUN_TARGET_NAME="arkscript">
<envs>
<env name="MallocNanoZone" value="0" />
</envs>
Expand Down
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,17 @@
- list:intersection
- list:difference
- sys:version, has the version of the ArkScript VM
- IR inliner, running after the AST lowerer, before the IR optimiser
- new debugger command `scopes`, printing the last `n` scopes (default: 5)
- new CLI toggle `-f(no-)irinliner` to toggle the IR inliner pass (default: On)

### 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
- `POP_SCOPE` can take an argument to have a mode: by default, `0`, only pop the current scope ; if `1`, materialise the top of the stack if it's a reference
- the debugger does not trace the instructions of the code being run inside it (only instructions from the script are traced)
- the AST lowerer checks for `(breakpoint)` at the end of `begin` nodes to determine if a node is unused or terminal

### Removed

Expand Down
3 changes: 3 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions include/Ark/Compiler/Instructions.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ namespace Ark::internal
// @role Reset the current scope so that it is empty, and jump to a given location
RESET_SCOPE_JUMP = 0x25,

// @args mode: if 1: materialise the top of the stack if it's a reference, else: just destroy the scope
// @role Destroy the last local scope
POP_SCOPE = 0x26,

Expand Down
206 changes: 200 additions & 6 deletions include/Ark/Compiler/IntermediateRepresentation/Entity.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
#include <cinttypes>
#include <vector>
#include <string>
#include <optional>
#include <algorithm>

#include <Ark/Compiler/IntermediateRepresentation/Word.hpp>
#include <Ark/Compiler/Instructions.hpp>
Expand All @@ -37,61 +39,253 @@ namespace Ark::internal::IR
constexpr uint16_t MaxValueForSmallNumber = 0x0800;
static_assert(MaxValueForSmallNumber + MaxValueForSmallNumber - 1 == MaxValueForDualArg);

constexpr std::string_view AnonymousBlockName = "#anonymous";

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);

void replaceLabel(label_t 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 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
*
* @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);

[[nodiscard]] bool hasValidSourceLocation() const { return !m_source_file.empty(); }
void setRelatedResourceId(std::optional<uint16_t> id);

[[nodiscard]] const std::string& filename() const { return m_source_file; }
[[nodiscard]] bool hasValidSourceLocation() const { return !m_metadata.source_file.empty(); }

[[nodiscard]] std::size_t sourceLine() const { return m_source_line; }
[[nodiscard]] const std::string& filename() const { return m_metadata.source_file; }

[[nodiscard]] std::size_t sourceLine() const { return m_metadata.source_line; }

/**
* @brief Return the related constant/symbol id an IR Entity refers to (only populated for LOAD_FAST_BY_INDEX, CALL_SYMBOL_BY_INDEX, CALL_SYMBOL, and CALL)
*
* @return std::optional<uint16_t>
*/
[[nodiscard]] std::optional<uint16_t> relatedResourceId() const { return m_metadata.related_res_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<uint16_t> related_res_id; ///< Used by a few instructions to know the original symbol/constant id and deoptimize when necessary
} m_metadata;
};

using Block = std::vector<Entity>;
/**
* @brief Block of IR entities, with attached metadata
*/
struct Block
{
using vec_t = std::vector<Entity>;

struct Metadata
{
std::optional<std::string> 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
bool is_mutating_args { false };
} metadata;
vec_t data;

[[nodiscard]] std::string debugName() const
{
return metadata.name.value_or(std::string(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_mutating_args)
flags += std::string(flags.empty() ? "" : " ") + "mutating";

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) {
return a.kind() != IR::Kind::Label;
});

if (length <= 0)
return 0;
return static_cast<std::size_t>(length);
}
};
}

#endif // ARK_COMPILER_INTERMEDIATEREPRESENTATION_ENTITY_HPP
Loading
Loading