Grow stack automatically

This commit is contained in:
Eugen Wissner 2024-02-18 11:26:57 +01:00
parent df2494e145
commit 86d579e8d5
Signed by: belka
GPG Key ID: A27FDC1E8EE902C0
12 changed files with 262 additions and 146 deletions

5
CMakeLists.txt Normal file
View File

@ -0,0 +1,5 @@
cmake_minimum_required(VERSION 3.21)
project(Elna)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
add_executable(tester tests/runner.cpp)

8
README
View File

@ -35,3 +35,11 @@ factor = ident | number | "(" expression ")";
"!" - Write a line.
"?" - Read user input.
"odd" - The only function, returns whether a number is odd.
# Build and test
```sh
cmake -B build
make -C build
./build/bin/tester
```

View File

@ -39,40 +39,9 @@ file BINARY => SOURCES do |t|
sh({ 'DFLAGS' => (DFLAGS * ' ') }, 'dub', 'build', '--compiler=gdc')
end
task default: TESTS
task default: BINARY
desc 'Run all tests and check the results'
task test: TESTS
task test: BINARY do
TESTS.each do |test|
expected = Pathname
.new(test)
.sub_ext('.txt')
.sub(/^build\/[[:alpha:]]+\//, 'tests/expectations/')
.to_path
puts "Running #{test}"
if test.include? '/riscv/'
spike = [
'/opt/riscv/bin/spike',
'--isa=RV32IMAC',
'/opt/riscv/riscv32-unknown-elf/bin/pk',
test
]
diff = ['diff', '-Nur', '--color', expected, '-']
tail = ['tail', '-n', '1']
last_stdout, wait_threads = Open3.pipeline_r spike, tail, diff
else
raise 'Unsupported test platform'
end
print last_stdout.read
last_stdout.close
fail unless wait_threads.last.value.exitstatus.zero?
end
end
desc 'Run unittest blocks'
task unittest: SOURCES do |t|
sh('dub', 'test', '--compiler=gdc-12')

8
TODO Normal file
View File

@ -0,0 +1,8 @@
- Mark all classes as extern(C++) to interface with C++.
- Some nodes assert(false, "Not implemented") because their logic is handled
in the parent nodes. Move this logic to the nodes themselves.
- Implement multiplication and division.
- Split tester.cpp into a header and implementation files.
- The generate function in backend.d contains the whole compilation logic.
It's not a backend task.
- Output assembly.

View File

@ -15,34 +15,35 @@ struct ASTMapping
alias Node = .Node;
alias Definition = .Definition;
alias VariableDeclaration = .VariableDeclaration;
alias Statement = Array!(.Statement);
alias BangStatement = .Expression;
alias Statement = .Operand;
alias BangStatement = .Operand;
alias Block = .Definition;
alias Expression = .Expression;
alias Expression = .Operand;
alias Number = .Number;
alias Variable = .Variable;
alias BinaryExpression = .BinaryExpression;
alias Variable = .Number;
alias BinaryExpression = .Variable;
}
/**
* IR visitor.
*/
extern(C++)
interface IRVisitor
{
abstract void visit(Node) @nogc;
abstract void visit(Definition) @nogc;
abstract void visit(Expression) @nogc;
abstract void visit(Statement) @nogc;
abstract void visit(Operand) @nogc;
abstract void visit(BinaryExpression) @nogc;
abstract void visit(Variable) @nogc;
abstract void visit(VariableDeclaration) @nogc;
abstract void visit(Number) @nogc;
abstract void visit(BinaryExpression) @nogc;
}
/**
* AST node.
*/
extern(C++)
abstract class Node
{
abstract void accept(IRVisitor) @nogc;
@ -51,11 +52,13 @@ abstract class Node
/**
* Definition.
*/
extern(C++)
class Definition : Node
{
char[] identifier;
Array!Statement statements;
Array!BinaryExpression statements;
Array!VariableDeclaration variableDeclarations;
Operand result;
override void accept(IRVisitor visitor) @nogc
{
@ -63,17 +66,13 @@ class Definition : Node
}
}
class Statement : Node
extern(C++)
abstract class Statement : Node
{
BinaryExpression expression;
override void accept(IRVisitor visitor) @nogc
{
visitor.visit(this);
}
}
abstract class Expression : Node
extern(C++)
abstract class Operand : Node
{
override void accept(IRVisitor visitor) @nogc
{
@ -81,7 +80,8 @@ abstract class Expression : Node
}
}
class Number : Expression
extern(C++)
class Number : Operand
{
int value;
@ -91,7 +91,8 @@ class Number : Expression
}
}
class Variable : Expression
extern(C++)
class Variable : Operand
{
size_t counter;
@ -101,6 +102,7 @@ class Variable : Expression
}
}
extern(C++)
class VariableDeclaration : Node
{
String identifier;
@ -111,12 +113,13 @@ class VariableDeclaration : Node
}
}
class BinaryExpression : Node
extern(C++)
class BinaryExpression : Statement
{
Expression lhs, rhs;
Operand lhs, rhs;
BinaryOperator operator;
this(Expression lhs, Expression rhs, BinaryOperator operator)
this(Operand lhs, Operand rhs, BinaryOperator operator)
@nogc
{
this.lhs = lhs;
@ -130,9 +133,26 @@ class BinaryExpression : Node
}
}
extern(C++)
class BangExpression : Statement
{
Operand operand;
this(Operand operand)
{
this.operand = operand;
}
override void accept(IRVisitor visitor) @nogc
{
visitor.visit(this);
}
}
final class TransformVisitor : parser.ParserVisitor!ASTMapping
{
private HashTable!(String, int) constants;
private Array!BinaryExpression statements;
ASTMapping.Node visit(parser.Node node) @nogc
{
@ -151,7 +171,7 @@ final class TransformVisitor : parser.ParserVisitor!ASTMapping
ASTMapping.BangStatement visit(parser.BangStatement statement) @nogc
{
assert(false, "Not implemented");
return statement.expression.accept(this);
}
ASTMapping.Block visit(parser.Block block) @nogc
@ -159,7 +179,9 @@ final class TransformVisitor : parser.ParserVisitor!ASTMapping
auto target = defaultAllocator.make!Definition;
this.constants = transformConstants(block.definitions);
target.statements = block.statement.accept(this);
target.result = block.statement.accept(this);
target.statements = this.statements;
target.variableDeclarations = transformVariableDeclarations(block.variableDeclarations);
return target;
@ -167,41 +189,45 @@ final class TransformVisitor : parser.ParserVisitor!ASTMapping
ASTMapping.Expression visit(parser.Expression expression) @nogc
{
assert(false, "Not implemented");
if ((cast(parser.Number) expression) !is null)
{
return (cast(parser.Number) expression).accept(this);
}
if ((cast(parser.Variable) expression) !is null)
{
return (cast(parser.Variable) expression).accept(this);
}
else if ((cast(parser.BinaryExpression) expression) !is null)
{
return (cast(parser.BinaryExpression) expression).accept(this);
}
assert(false, "Invalid expression type");
}
ASTMapping.Number visit(parser.Number number) @nogc
{
assert(false, "Not implemented");
auto numberExpression = defaultAllocator.make!Number;
numberExpression.value = number.value;
return numberExpression;
}
ASTMapping.Variable visit(parser.Variable variable) @nogc
{
assert(false, "Not implemented");
auto numberExpression = defaultAllocator.make!Number;
numberExpression.value = this.constants[variable.identifier];
return numberExpression;
}
ASTMapping.BinaryExpression visit(parser.BinaryExpression) @nogc
{
assert(false, "Not implemented");
}
private Number transformNumber(parser.Number number) @nogc
{
return defaultAllocator.make!Number(number.value);
}
private Variable binaryExpression(parser.BinaryExpression binaryExpression,
ref Array!Statement statements) @nogc
ASTMapping.BinaryExpression visit(parser.BinaryExpression binaryExpression) @nogc
{
auto target = defaultAllocator.make!BinaryExpression(
expression(binaryExpression.lhs, statements),
expression(binaryExpression.rhs, statements),
binaryExpression.lhs.accept(this),
binaryExpression.rhs.accept(this),
binaryExpression.operator
);
auto newStatement = defaultAllocator.make!Statement;
newStatement.expression = target;
statements.insertBack(newStatement);
statements.insertBack(target);
auto newVariable = defaultAllocator.make!Variable;
newVariable.counter = statements.length;
@ -209,38 +235,18 @@ final class TransformVisitor : parser.ParserVisitor!ASTMapping
return newVariable;
}
private Expression expression(parser.Expression expression,
ref Array!Statement statements) @nogc
private Number transformNumber(parser.Number number) @nogc
{
if ((cast(parser.Number) expression) !is null)
{
auto numberExpression = defaultAllocator.make!Number;
numberExpression.value = (cast(parser.Number) expression).value;
return numberExpression;
}
if ((cast(parser.Variable) expression) !is null)
{
auto numberExpression = defaultAllocator.make!Number;
numberExpression.value = this.constants[(cast(parser.Variable) expression).identifier];
return numberExpression;
}
else if ((cast(parser.BinaryExpression) expression) !is null)
{
return binaryExpression(cast(parser.BinaryExpression) expression, statements);
}
return null;
return defaultAllocator.make!Number(number.value);
}
override Array!Statement visit(parser.Statement statement) @nogc
override Operand visit(parser.Statement statement) @nogc
{
typeof(return) statements;
if ((cast(parser.BangStatement) statement) !is null)
{
expression((cast(parser.BangStatement) statement).expression, statements);
return (cast(parser.BangStatement) statement).accept(this);
}
return statements;
assert(false, "Invalid statement type");
}
private HashTable!(String, int) transformConstants(ref Array!(parser.Definition) definitions) @nogc

View File

@ -8,6 +8,7 @@ import std.range;
import tanya.container.array;
import tanya.container.string;
extern(C++)
struct Token
{
enum Type
@ -71,7 +72,7 @@ struct Token
}
@property auto value(Type type)() @nogc nothrow pure @trusted
in (ofType(type))
in (ofType(type), "Expected type: " ~ type.stringof)
{
static if (type == Type.number)
{
@ -99,14 +100,17 @@ struct Token
/**
* Range over the source text that keeps track of the current position.
*/
extern(C++)
struct Source
{
char[] buffer;
char* buffer_;
size_t length_;
Position position;
this(char[] buffer) @nogc nothrow pure @safe
this(char* buffer, const size_t length) @nogc nothrow pure
{
this.buffer = buffer;
this.buffer_ = buffer;
this.length_ = length;
}
@disable this();
@ -116,54 +120,50 @@ struct Source
return this.length == 0;
}
char front() @nogc nothrow pure @safe
char front() @nogc nothrow pure
in (!empty)
{
return this.buffer[0];
return this.buffer_[0];
}
void popFront() @nogc nothrow pure @safe
void popFront() @nogc nothrow pure
in (!empty)
{
this.buffer = buffer[1 .. $];
++this.buffer_;
--this.length_;
++this.position.column;
}
void breakLine() @nogc nothrow pure @safe
void breakLine() @nogc nothrow pure
in (!empty)
{
this.buffer = buffer[1 .. $];
++this.buffer_;
--this.length_;
++this.position.line;
this.position.column = 1;
}
@property size_t length() const @nogc nothrow pure @safe
{
return this.buffer.length;
return this.length_;
}
char opIndex(size_t index) @nogc nothrow pure @safe
char opIndex(size_t index) @nogc nothrow pure
in (index < length)
{
return this.buffer[index];
return this.buffer_[index];
}
char[] opSlice(size_t i, size_t j) @nogc nothrow pure @safe
in
char* buffer() @nogc nothrow pure
{
assert(i <= j);
assert(j <= length);
}
do
{
return this.buffer[i .. j];
return this.buffer_;
}
}
Result!(Array!Token) lex(char[] buffer) @nogc
{
Array!Token tokens;
auto source = Source(buffer);
auto source = Source(buffer.ptr, buffer.length);
while (!source.empty)
{
@ -218,17 +218,17 @@ Result!(Array!Token) lex(char[] buffer) @nogc
{
++i;
}
if (source[0 .. i] == "const")
if (source.buffer[0 .. i] == "const")
{
tokens.insertBack(Token(Token.Type.let, source.position));
}
else if (source[0 .. i] == "var")
else if (source.buffer[0 .. i] == "var")
{
tokens.insertBack(Token(Token.Type.var, source.position));
}
else
{
auto identifier = String(source[0 .. i]);
auto identifier = String(source.buffer[0 .. i]);
tokens.insertBack(Token(Token.Type.identifier, identifier, source.position));
}
source.popFrontN(i);

View File

@ -1,5 +1,6 @@
module elna.riscv;
import core.stdc.stdlib;
import elna.extended;
import elna.ir;
import elna.result;
@ -7,7 +8,6 @@ import std.algorithm;
import std.typecons;
import tanya.container.array;
import tanya.container.string;
import tanya.memory.allocator;
enum XRegister : ubyte
{
@ -140,14 +140,14 @@ struct Instruction
return this;
}
ref Instruction s(uint imm1, Funct3 funct3, XRegister rs1, XRegister rs2, uint imm2 = 0)
ref Instruction s(uint imm1, Funct3 funct3, XRegister rs1, XRegister rs2)
return scope @nogc
{
this.instruction |= (imm1 << 7)
this.instruction |= ((imm1 & 0b11111) << 7)
| (funct3 << 12)
| (rs1 << 15)
| (rs2 << 20)
| (imm2 << 25);
| ((imm1 & 0b111111100000) << 20);
return this;
}
@ -178,6 +178,7 @@ struct Instruction
}
}
extern(C++)
class RiscVVisitor : IRVisitor
{
Array!Instruction instructions;
@ -191,22 +192,24 @@ class RiscVVisitor : IRVisitor
override void visit(Definition definition) @nogc
{
const uint stackSize = cast(uint) (definition.statements.length * 4 + 12);
// Prologue.
this.instructions.insertBack(
Instruction(BaseOpcode.opImm)
.i(XRegister.sp, Funct3.addi, XRegister.sp, cast(uint) -32)
.i(XRegister.sp, Funct3.addi, XRegister.sp, -stackSize)
);
this.instructions.insertBack(
Instruction(BaseOpcode.store)
.s(28, Funct3.sw, XRegister.sp, XRegister.s0)
.s(stackSize - 4, Funct3.sw, XRegister.sp, XRegister.s0)
);
this.instructions.insertBack(
Instruction(BaseOpcode.store)
.s(24, Funct3.sw, XRegister.sp, XRegister.ra)
.s(stackSize - 8, Funct3.sw, XRegister.sp, XRegister.ra)
);
this.instructions.insertBack(
Instruction(BaseOpcode.opImm)
.i(XRegister.s0, Funct3.addi, XRegister.sp, 32)
.i(XRegister.s0, Funct3.addi, XRegister.sp, stackSize)
);
foreach (statement; definition.statements[])
@ -217,6 +220,10 @@ class RiscVVisitor : IRVisitor
{
variableDeclaration.accept(this);
}
this.registerInUse = true;
definition.result.accept(this);
this.registerInUse = false;
// Print the result.
this.instructions.insertBack(
Instruction(BaseOpcode.opImm)
@ -247,15 +254,15 @@ class RiscVVisitor : IRVisitor
// Epilogue.
this.instructions.insertBack(
Instruction(BaseOpcode.load)
.i(XRegister.s0, Funct3.lw, XRegister.sp, 28)
.i(XRegister.s0, Funct3.lw, XRegister.sp, stackSize - 4)
);
this.instructions.insertBack(
Instruction(BaseOpcode.load)
.i(XRegister.ra, Funct3.lw, XRegister.sp, 24)
.i(XRegister.ra, Funct3.lw, XRegister.sp, stackSize - 8)
);
this.instructions.insertBack(
Instruction(BaseOpcode.opImm)
.i(XRegister.sp, Funct3.addi, XRegister.sp, 32)
.i(XRegister.sp, Funct3.addi, XRegister.sp, stackSize)
);
this.instructions.insertBack(
Instruction(BaseOpcode.jalr)
@ -263,13 +270,16 @@ class RiscVVisitor : IRVisitor
);
}
override void visit(Expression) @nogc
override void visit(Operand operand) @nogc
{
}
override void visit(Statement statement) @nogc
{
statement.expression.accept(this);
if ((cast(Variable) operand) !is null)
{
return (cast(Variable) operand).accept(this);
}
if ((cast(Number) operand) !is null)
{
return (cast(Number) operand).accept(this);
}
}
override void visit(Variable variable) @nogc
@ -334,10 +344,12 @@ Symbol writeNext(Definition ast) @nogc
{
Array!Instruction instructions;
Array!Reference references;
auto visitor = defaultAllocator.make!RiscVVisitor;
auto visitor = cast(RiscVVisitor) malloc(__traits(classInstanceSize, RiscVVisitor));
(cast(void*) visitor)[0 .. __traits(classInstanceSize, RiscVVisitor)] = __traits(initSymbol, RiscVVisitor)[];
scope (exit)
{
defaultAllocator.dispose(visitor);
visitor.__xdtor();
free(cast(void*) visitor);
}
visitor.visit(ast);

1
tests/7_member_sum.eln Normal file
View File

@ -0,0 +1 @@
! 3 + 4 + 5 + 1 + 2 + 4 + 3

View File

@ -0,0 +1 @@
22

View File

@ -0,0 +1 @@
3

2
tests/print_number.eln Normal file
View File

@ -0,0 +1,2 @@
! 3
.

103
tests/runner.cpp Executable file
View File

@ -0,0 +1,103 @@
#include <boost/process.hpp>
#include <iostream>
#include <filesystem>
class test_results final
{
std::uint32_t m_total{ 0 };
std::uint32_t m_passed{ 0 };
public:
test_results() = default;
std::uint32_t total() const noexcept
{
return m_total;
}
std::uint32_t passed() const noexcept
{
return m_passed;
}
std::uint32_t failed() const noexcept
{
return m_total - m_passed;
}
int exit_code() const noexcept
{
return m_total == m_passed ? EXIT_SUCCESS : EXIT_FAILURE;
}
void add_exit_code(const int exit_code) noexcept
{
++m_total;
if (exit_code == 0)
{
++m_passed;
}
}
};
int run_test(const std::filesystem::directory_entry& test_entry)
{
const std::filesystem::path test_filename = test_entry.path().filename();
std::filesystem::path test_binary = "build/riscv" / test_filename;
test_binary.replace_extension();
std::filesystem::path expectation_path = test_entry.path().parent_path() / "expectations" / test_filename;
expectation_path.replace_extension(".txt");
std::cout << "Running " << test_binary << std::endl;
boost::process::pipe pipe_stream;
boost::process::child vm(
"/opt/riscv/bin/spike", "--isa=RV32IMAC",
"/opt/riscv/riscv32-unknown-elf/bin/pk",
test_binary.string(),
boost::process::std_out > pipe_stream
);
boost::process::child diff(
"/usr/bin/diff", "-Nur", "--color",
expectation_path.string(), "-",
boost::process::std_in < pipe_stream
);
vm.wait();
diff.wait();
return diff.exit_code();
}
test_results run_in_path(const std::filesystem::path test_directory)
{
test_results results;
for (const auto& test_entry : std::filesystem::directory_iterator(test_directory))
{
if (test_entry.path().extension() != ".eln" || !test_entry.is_regular_file())
{
continue;
}
results.add_exit_code(run_test(test_entry));
}
return results;
}
int main()
{
boost::process::system("rake");
std::cout << "Run all tests and check the results" << std::endl;
std::filesystem::path test_directory{ "tests" };
const auto results = run_in_path(test_directory);
std::cout << std::endl;
std::cout << results.total() << " tests run, "
<< results.passed() << " passed, "
<< results.failed() << " failed." << std::endl;
return results.exit_code();
}