Skip to content

Commit

Permalink
feat(tests): improve testing of IR generation and optimization
Browse files Browse the repository at this point in the history
  • Loading branch information
SuperFola committed Nov 4, 2024
1 parent 83049c2 commit eefeb78
Show file tree
Hide file tree
Showing 21 changed files with 632 additions and 13 deletions.
3 changes: 2 additions & 1 deletion include/Ark/Compiler/Compiler.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* @file Compiler.hpp
* @author Alexandre Plateau ([email protected])
* @brief ArkScript compiler is in charge of transforming the AST into bytecode
* @version 3.0
* @version 3.1
* @date 2020-10-27
*
* @copyright Copyright (c) 2020-2024
Expand Down Expand Up @@ -83,6 +83,7 @@ namespace Ark::internal
std::vector<ValTableElem> m_values;
std::vector<IR::Block> m_code_pages;
std::vector<IR::Block> m_temp_pages; ///< we need temporary code pages for some compilations passes
IR::label_t m_current_label = 0;

unsigned m_debug; ///< the debug level of the compiler
Logger m_logger;
Expand Down
6 changes: 2 additions & 4 deletions include/Ark/Compiler/IntermediateRepresentation/Entity.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* @file Entity.hpp
* @author Alexandre Plateau ([email protected])
* @brief An entity in the IR is a bundle of information
* @version 0.1
* @version 1.0
* @date 2024-10-05
*
* @copyright Copyright (c) 2024
Expand Down Expand Up @@ -44,7 +44,7 @@ namespace Ark::internal::IR

Entity(Instruction inst, uint16_t primary_arg, uint16_t secondary_arg);

static Entity Label();
static Entity Label(label_t value);

static Entity Goto(const Entity& label);

Expand All @@ -63,8 +63,6 @@ namespace Ark::internal::IR
[[nodiscard]] inline uint16_t secondaryArg() const { return m_secondary_arg; }

private:
inline static label_t LabelCounter = 0;

Kind m_kind;
label_t m_label { 0 };
Instruction m_inst { NOP };
Expand Down
10 changes: 5 additions & 5 deletions src/arkreactor/Compiler/Compiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -285,15 +285,15 @@ namespace Ark::internal
compileExpression(x.constList()[1], p, false, false);

// jump only if needed to the if
const auto label_then = IR::Entity::Label();
const auto label_then = IR::Entity::Label(m_current_label++);
page(p).emplace_back(IR::Entity::GotoIf(label_then, true));

// else code
if (x.constList().size() == 4) // we have an else clause
compileExpression(x.constList()[3], p, is_result_unused, is_terminal, var_name);

// when else is finished, jump to end
const auto label_end = IR::Entity::Label();
const auto label_end = IR::Entity::Label(m_current_label++);
page(p).emplace_back(IR::Entity::Goto(label_end));

// absolute address to jump to if condition is true
Expand Down Expand Up @@ -376,12 +376,12 @@ namespace Ark::internal
throwCompilerError("Invalid node ; if it was computed by a macro, check that a node is returned", x);

// save current position to jump there at the end of the loop
const auto label_loop = IR::Entity::Label();
const auto label_loop = IR::Entity::Label(m_current_label++);
page(p).emplace_back(label_loop);
// push condition
compileExpression(x.constList()[1], p, false, false);
// absolute jump to end of block if condition is false
const auto label_end = IR::Entity::Label();
const auto label_end = IR::Entity::Label(m_current_label++);
page(p).emplace_back(IR::Entity::GotoIf(label_end, false));
// push code to page
compileExpression(x.constList()[2], p, true, false);
Expand Down Expand Up @@ -439,7 +439,7 @@ namespace Ark::internal
compileExpression(x.constList()[1], p, false, false);
page(p).emplace_back(DUP);

const auto label_shortcircuit = IR::Entity::Label();
const auto label_shortcircuit = IR::Entity::Label(m_current_label++);
for (std::size_t i = 2, end = x.constList().size(); i < end; ++i)
{
switch (maybe_shortcircuit.value())
Expand Down
4 changes: 2 additions & 2 deletions src/arkreactor/Compiler/IntermediateRepresentation/Entity.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ namespace Ark::internal::IR
m_inst(inst), m_primary_arg(primary_arg), m_secondary_arg(secondary_arg)
{}

Entity Entity::Label()
Entity Entity::Label(const label_t value)
{
auto label = Entity(Kind::Label);
label.m_label = Entity::LabelCounter++;
label.m_label = value;

return label;
}
Expand Down
24 changes: 23 additions & 1 deletion tests/unittests/CompilerSuite.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,33 @@ ut::suite<"Compiler"> compiler_suite = [] {
};
};

"IR generation"_test = [] {
constexpr uint16_t features = Ark::FeatureImportSolver | Ark::FeatureMacroProcessor | Ark::FeatureASTOptimizer | Ark::FeatureNameResolver | Ark::FeatureTestFailOnException;

iter_test_files(
"CompilerSuite/ir",
[](TestData&& data) {
Ark::Welder welder(0, { std::filesystem::path(ARK_TESTS_ROOT "/lib/") }, features);

should("compile without error ir/" + data.stem) = [&] {
expect(mut(welder).computeASTFromFile(data.path));
expect(mut(welder).generateBytecode());
};

should("output expected IR for " + data.stem) = [&] {
std::string ir = welder.textualIR();

ltrim(rtrim(ir));
expect(that % ir == data.expected);
};
});
};

"IR generation and optimization"_test = [] {
constexpr uint16_t features = Ark::DefaultFeatures | Ark::FeatureTestFailOnException;

iter_test_files(
"CompilerSuite/ir",
"CompilerSuite/optimized_ir",
[](TestData&& data) {
Ark::Welder welder(0, { std::filesystem::path(ARK_TESTS_ROOT "/lib/") }, features);

Expand Down
21 changes: 21 additions & 0 deletions tests/unittests/resources/CompilerSuite/ir/99bottles.ark
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Lyrics from the song:
#
# 99 bottles of beer on the wall
# 99 bottles of beer
# Take one down, pass it around
# 98 bottles of beer on the wall
#
# 98 bottles of beer on the wall
# 98 bottles of beer
# Take one down, pass it around
# 97 bottles of beer on the wall


(let arg (if (>= (len sys:args) 1) (toNumber (@ sys:args 0)) nil))
(let i (if (nil? arg) 100 arg))

(mut n i)
(while (> n 1) {
(print (str:format "{} Bottles of beer on the wall\n{} bottles of beer\nTake one down, pass it around" n n))
(set n (- n 1))
(print (str:format "{} Bottles of beer on the wall." n))})
53 changes: 53 additions & 0 deletions tests/unittests/resources/CompilerSuite/ir/99bottles.expected
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
page_0
LOAD_SYMBOL 1
LEN 0
LOAD_CONST 0
GE 0
GOTO_IF_TRUE L0
BUILTIN 2
GOTO L1
.L0:
LOAD_SYMBOL 1
LOAD_CONST 1
AT 0
TO_NUM 0
.L1:
STORE 0
LOAD_SYMBOL 0
ISNIL 0
GOTO_IF_TRUE L2
LOAD_SYMBOL 0
GOTO L3
.L2:
LOAD_CONST 2
.L3:
STORE 2
LOAD_SYMBOL 2
STORE 3
.L4:
LOAD_SYMBOL 3
LOAD_CONST 0
GT 0
GOTO_IF_FALSE L5
LOAD_CONST 3
LOAD_SYMBOL 3
LOAD_SYMBOL 3
BUILTIN 24
CALL 3
BUILTIN 9
CALL 1
POP 0
LOAD_SYMBOL 3
LOAD_CONST 0
SUB 0
SET_VAL 3
LOAD_CONST 4
LOAD_SYMBOL 3
BUILTIN 24
CALL 2
BUILTIN 9
CALL 1
POP 0
GOTO L4
.L5:
HALT 0
49 changes: 49 additions & 0 deletions tests/unittests/resources/CompilerSuite/ir/ackermann.expected
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
page_0
LOAD_CONST 0
STORE 0
LOAD_CONST 3
LOAD_CONST 4
LOAD_CONST 5
LOAD_SYMBOL 0
CALL 2
BUILTIN 9
CALL 2
HALT 0

page_1
STORE 1
STORE 2
LOAD_SYMBOL 1
LOAD_CONST 1
GT 0
GOTO_IF_TRUE L0
LOAD_CONST 2
LOAD_SYMBOL 2
ADD 0
GOTO L1
.L0:
LOAD_CONST 1
LOAD_SYMBOL 2
EQ 0
GOTO_IF_TRUE L2
LOAD_SYMBOL 1
LOAD_SYMBOL 2
LOAD_CONST 2
SUB 0
LOAD_SYMBOL 0
CALL 2
LOAD_SYMBOL 1
LOAD_CONST 2
SUB 0
JUMP 0
GOTO L3
.L2:
LOAD_CONST 2
LOAD_SYMBOL 1
LOAD_CONST 2
SUB 0
JUMP 0
.L3:
.L1:
RET 0
HALT 0
46 changes: 46 additions & 0 deletions tests/unittests/resources/CompilerSuite/ir/closures.ark
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Inspired by
# Closures and object are equivalent: http://wiki.c2.com/?ClosuresAndObjectsAreEquivalent

# this will construct a closure capturing the 3 arguments, plus a function to set the age
(let create-human (fun (name age weight) {
# functions can be invoked in the closure scope
(let set-age (fun (new-age) (set age new-age)))

# the return value, our closure
# the &name notation is used in the argument list to explicitly capture
# a variable (using deep copy)
(fun (&set-age &name &age &weight) ())}))

# we create 2 humans using such construction, just a nice function call
(let bob (create-human "Bob" 0 144))
(let john (create-human "John" 12 15))

# using the dot notation on a closure object, we can have a **read only** access to its fields
(print "Bob's age: " bob.age)
# and even call the captured functions, which will enter the closure, and be given a **read write** access
# meaning that, even if the fields are read only (eg, we can not do (set bob.age 14)), the "object" can be modified
(print "Setting Bob's age to 10")
(bob.set-age 10)
# the age changed
(print "New age: " bob.age)

# but john age didn't change, because we created 2 separated closures
(print "John's age, didn't change: " john.age)



# Another example to simulate a python range(x, y)

# this function will return a closure capturing the number given
# and modifying its value each time we'll call the closure, returning
# the new number
(let countdown-from (fun (number)
(fun (&number) {
(set number (- number 1))
number })))

(let countdown-from-3 (countdown-from 3))

(print "Countdown " (countdown-from-3)) # 2
(print "Countdown " (countdown-from-3)) # 1
(print "Countdown " (countdown-from-3)) # 0
Loading

0 comments on commit eefeb78

Please sign in to comment.