From f37700a02d5eec5395589c202c205af697cd7a5d Mon Sep 17 00:00:00 2001 From: Eugen Wissner Date: Sun, 5 Jun 2022 23:43:45 +0200 Subject: [PATCH] Print test summary --- .gitignore | 6 +- CMakeLists.txt | 25 + Rakefile | 56 +- TODO | 23 + dub.json | 6 +- include/elna/history.hpp | 28 + include/elna/interactive.hpp | 89 ++ include/elna/ir.hpp | 95 ++ include/elna/lexer.hpp | 109 +++ include/elna/parser.hpp | 10 + include/elna/result.hpp | 62 ++ include/elna/riscv.hpp | 148 +++ include/elna/state.hpp | 170 ++++ include/elna/tester.hpp | 22 + shell/history.cpp | 54 ++ shell/interactive.cpp | 286 ++++++ shell/main.cpp | 16 + shell/state.cpp | 254 +++++ source/elna/arguments.d | 154 +++ source/elna/backend.d | 114 +++ source/elna/elf.d | 1060 +++++++++++++++++++++ source/elna/extended.d | 328 ++++++- source/elna/generator.d | 660 ------------- source/elna/ir.d | 262 +++-- source/elna/lexer.d | 207 +--- source/elna/parser.d | 273 +++--- source/elna/result.d | 52 +- source/elna/riscv.d | 183 ++++ source/ir.cpp | 53 ++ source/lexer.cpp | 270 ++++++ source/main.d | 83 +- source/result.cpp | 25 + source/riscv.cpp | 191 ++++ tests/7_member_sum.eln | 1 + tests/{const_list.elna => const_list.eln} | 2 +- tests/expectations/7_member_sum.txt | 1 + tests/expectations/left_nested_sum.txt | 1 + tests/expectations/print_number.txt | 1 + tests/expectations/subtraction.txt | 1 + tests/left_nested_sum.eln | 2 + tests/print_number.eln | 2 + tests/runner.cpp | 151 +++ tests/subtraction.eln | 2 + tests/sum.eln | 2 + tests/sum.elna | 2 - tests/sums.eln | 2 + tests/sums.elna | 2 - 47 files changed, 4362 insertions(+), 1184 deletions(-) create mode 100644 CMakeLists.txt create mode 100644 TODO create mode 100644 include/elna/history.hpp create mode 100644 include/elna/interactive.hpp create mode 100644 include/elna/ir.hpp create mode 100644 include/elna/lexer.hpp create mode 100644 include/elna/parser.hpp create mode 100644 include/elna/result.hpp create mode 100644 include/elna/riscv.hpp create mode 100644 include/elna/state.hpp create mode 100644 include/elna/tester.hpp create mode 100644 shell/history.cpp create mode 100644 shell/interactive.cpp create mode 100644 shell/main.cpp create mode 100644 shell/state.cpp create mode 100644 source/elna/arguments.d create mode 100644 source/elna/backend.d create mode 100644 source/elna/elf.d delete mode 100644 source/elna/generator.d create mode 100644 source/elna/riscv.d create mode 100644 source/ir.cpp create mode 100644 source/lexer.cpp create mode 100644 source/result.cpp create mode 100644 source/riscv.cpp create mode 100644 tests/7_member_sum.eln rename tests/{const_list.elna => const_list.eln} (73%) create mode 100644 tests/expectations/7_member_sum.txt create mode 100644 tests/expectations/left_nested_sum.txt create mode 100644 tests/expectations/print_number.txt create mode 100644 tests/expectations/subtraction.txt create mode 100644 tests/left_nested_sum.eln create mode 100644 tests/print_number.eln create mode 100755 tests/runner.cpp create mode 100644 tests/subtraction.eln create mode 100644 tests/sum.eln delete mode 100644 tests/sum.elna create mode 100644 tests/sums.eln delete mode 100644 tests/sums.elna diff --git a/.gitignore b/.gitignore index d0d201a..021b743 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ -/.dub/ -/dub.selections.json /build/ +.cache/ +CMakeFiles/ +CMakeCache.txt +dub.selections.json diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..eac0f7f --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,25 @@ +cmake_minimum_required(VERSION 3.21) +project(Elna) + +set(CMAKE_EXPORT_COMPILE_COMMANDS 1) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) +set(CMAKE_CXX_STANDARD 17) + +add_executable(tester tests/runner.cpp include/elna/tester.hpp) +target_include_directories(tester PRIVATE include) + +add_executable(elnsh shell/main.cpp + shell/interactive.cpp include/elna/interactive.hpp + shell/history.cpp include/elna/history.hpp + shell/state.cpp include/elna/state.hpp +) +target_include_directories(elnsh PRIVATE include) + +add_library(elna + source/lexer.cpp include/elna/lexer.hpp + source/result.cpp include/elna/result.hpp + source/riscv.cpp include/elna/riscv.hpp + source/ir.cpp include/elna/ir.hpp + include/elna/parser.hpp +) +target_include_directories(elna PRIVATE include) diff --git a/Rakefile b/Rakefile index acafe3b..773d560 100644 --- a/Rakefile +++ b/Rakefile @@ -4,66 +4,16 @@ require 'open3' DFLAGS = ['--warn-no-deprecated', '-L/usr/lib64/gcc-12'] BINARY = 'build/bin/elna' -TESTS = FileList['tests/*.elna'] - .map { |test| (Pathname.new('build') + test).sub_ext('').to_path } SOURCES = FileList['source/**/*.d'] -directory 'build' +directory 'build/riscv' CLEAN.include 'build' CLEAN.include '.dub' -rule(/build\/tests\/.+/ => ->(file) { test_for_out(file) }) do |t| - Pathname.new(t.name).dirname.mkpath - sh BINARY, t.source - sh 'gcc', '-o', t.name, "#{t.name}.o" - # Open3.pipeline [BINARY, t.source], ['gcc', '-x', 'assembler', '-o', t.name, '-'] -end - file BINARY => SOURCES do |t| - sh({ 'DFLAGS' => (DFLAGS * ' ') }, 'dub', 'build', '--compiler=gdc-12') -end - -file 'build/tests/sample' => BINARY do |t| - sh t.source - sh 'gcc', '-o', t.name, 'build/tests/sample.o' + sh({ 'DFLAGS' => (DFLAGS * ' ') }, 'dub', 'build', '--compiler=gdc') end +task default: 'build/riscv' 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\/tests\//, 'tests/expectations/') - .read - .to_i - - puts "Running #{test}" - system test - actual = $?.exitstatus - - fail "#{test}: Expected #{expected}, got #{actual}" unless expected == actual - end - - # system './build/tests/sample' - # actual = $?.exitstatus - # fail "./build/tests/sample: Expected 3, got #{actual}" unless 3 == actual -end - -desc 'Run unittest blocks' -task unittest: SOURCES do |t| - sh('dub', 'test', '--compiler=gdc-12') -end - -def test_for_out(out_file) - test_source = Pathname - .new(out_file) - .sub_ext('.elna') - .sub(/^build\//, '') - .to_path - [test_source, BINARY] -end diff --git a/TODO b/TODO new file mode 100644 index 0000000..6a08d34 --- /dev/null +++ b/TODO @@ -0,0 +1,23 @@ +# Completion + +- Configure fzf to show only the current directory files +- Support multiple selections for insertion in fzf. +- Don't hardcode fzf binary path. +- Send the word under the cursor to fzf as initial input. +- Home directory expansion. +- Show files in the PATH if starting at the beginning of the prompt + +# Appearance + +- Add a bar with additional information under the prompt (edit_bar), like the hostname. +- Show current time in the prompt. + +# Input + +- DEL handling. +- Starting long running process and killing it with Ctrl-C kills the shell. + +# Other + +- Persist the history. +- Replace hard coded ANSI codes with constants or functions. diff --git a/dub.json b/dub.json index 8567d3a..2e76980 100644 --- a/dub.json +++ b/dub.json @@ -1,9 +1,11 @@ { "dependencies": { - "tanya": "~>0.18.0" + "tanya": "~>0.19.0" }, "name": "elna", "targetType": "executable", "targetPath": "build/bin", - "mainSourceFile": "source/main.d" + "mainSourceFile": "source/main.d", + "libs": ["elna", "stdc++"], + "lflags": ["-Lbuild"] } diff --git a/include/elna/history.hpp b/include/elna/history.hpp new file mode 100644 index 0000000..c7fb3d4 --- /dev/null +++ b/include/elna/history.hpp @@ -0,0 +1,28 @@ +#pragma once + +#include +#include +#include + +namespace elna +{ + struct editor_history : public boost::noncopyable + { + using const_iterator = std::list::const_iterator; + + editor_history(); + + void push(const std::string& entry); + void clear(); + + const_iterator next() noexcept; + const_iterator prev() noexcept; + const_iterator cbegin() const noexcept; + const_iterator cend() const noexcept; + const_iterator current() const noexcept; + + private: + std::list commands; + std::list::const_iterator current_pointer; + }; +} diff --git a/include/elna/interactive.hpp b/include/elna/interactive.hpp new file mode 100644 index 0000000..8106060 --- /dev/null +++ b/include/elna/interactive.hpp @@ -0,0 +1,89 @@ +#pragma once + +#include +#include +#include +#include + +#include "elna/state.hpp" +#include "elna/history.hpp" + +#define BOOST_PROCESS_USE_STD_FS + +namespace elna +{ + /** + * Runtime exception for non recoverable errors. + */ + struct interactive_exception : public std::runtime_error + { + /** + * Constructor. + */ + interactive_exception(); + }; + + /** + * Main loop. + */ + void loop(); + + /** + * Reads the next line. + * Returns no value upon receiving end of file. + * + * \param history Shell history. + * \return The read line. + */ + std::optional read_line(editor_history& history); + + /** + * Runs a built-in or a command. + * + * \param line The command and arguments. + * \return Whether the input shoud continued (no exit requested). + */ + bool execute(const std::string& line); + + /** + * Runs the binary specified in its argument. + * + * \param program Program name in PATH. + */ + void launch(const std::string& program); + + /** + * Enables the raw mode. + * + * \return Whether the operation was successful. + */ + bool enable_raw_mode(); + + /** + * Disables the raw mode. + */ + void disable_raw_mode(); + + /** + * Reads a key. + * + * \return Read character. + */ + key read_key(); + + /** + * Calls autocompletion. + * + * \param User input. + * \param Cursor position in the user input. + * \return Selected item. + */ + std::string complete(const std::string& input, const std::size_t position); + + /** + * Prints the message from the exception. + * + * \param exception The exception to print. + */ + void print_exception(const std::exception& exception); +} diff --git a/include/elna/ir.hpp b/include/elna/ir.hpp new file mode 100644 index 0000000..d758a73 --- /dev/null +++ b/include/elna/ir.hpp @@ -0,0 +1,95 @@ +#pragma once + +#include "elna/parser.hpp" +#include +#include + +namespace elna::ir +{ + class Node; + class Definition; + class Operand; + class BinaryExpression; + class Variable; + class VariableDeclaration; + class Number; + + struct IRVisitor + { + virtual void visit(Node *) = 0; + virtual void visit(Definition *) = 0; + virtual void visit(Operand *) = 0; + virtual void visit(BinaryExpression *) = 0; + virtual void visit(Variable *) = 0; + virtual void visit(Number *) = 0; + }; + + /** + * AST node. + */ + class Node + { + public: + virtual void accept(IRVisitor *) = 0; + }; + + /** + * Definition. + */ + class Definition : public Node + { + public: + BinaryExpression **statements; + std::size_t statementsLength; + Operand *result; + + virtual void accept(IRVisitor *visitor) override; + }; + + class Statement : public Node + { + }; + + class Operand : public Node + { + public: + virtual void accept(IRVisitor *visitor) override; + }; + + class Number : public Operand + { + public: + std::int32_t value; + + virtual void accept(IRVisitor *visitor) override; + }; + + class Variable : public Operand + { + public: + std::size_t counter; + + virtual void accept(IRVisitor *visitor) override; + }; + + class BinaryExpression : public Statement + { + public: + Operand *lhs, *rhs; + BinaryOperator _operator; + + BinaryExpression(Operand *lhs, Operand *rhs, BinaryOperator _operator); + + virtual void accept(IRVisitor *visitor) override; + }; + + class BangExpression : public Statement + { + Operand *operand; + + public: + BangExpression(Operand *operand); + + virtual void accept(IRVisitor *visitor) override; + }; +} diff --git a/include/elna/lexer.hpp b/include/elna/lexer.hpp new file mode 100644 index 0000000..09ab8a6 --- /dev/null +++ b/include/elna/lexer.hpp @@ -0,0 +1,109 @@ +#pragma once + +#include + +#include "elna/result.hpp" + +namespace elna +{ + /** + * Range over the source text that keeps track of the current position. + */ + struct source + { + class const_iterator + { + std::string::const_iterator m_buffer; + Position m_position; + + const_iterator(std::string::const_iterator buffer, + const Position position = Position()); + + public: + using iterator_category = std::forward_iterator_tag; + using difference_type = ptrdiff_t; + using value_type = char; + using pointer = const value_type *; + using reference = const value_type&; + + const Position& position() const noexcept; + + reference operator*() const noexcept; + pointer operator->() const noexcept; + const_iterator& operator++(); + const_iterator& operator++(int); + bool operator==(const const_iterator& that) const noexcept; + bool operator!=(const const_iterator& that) const noexcept; + + friend source; + }; + + source(const std::string& buffer); + const_iterator begin() const; + const_iterator end() const; + + private: + const std::string m_buffer; + }; + + /** + * Union type representing a single token. + */ + struct Token + { + /** + * Token type. + */ + enum Type : std::uint16_t + { + TOKEN_NUMBER = 0, + TOKEN_OPERATOR = 1, + TOKEN_LET = 2, + TOKEN_IDENTIFIER = 3, + TOKEN_EQUALS = 4, + TOKEN_VAR = 5, + TOKEN_SEMICOLON = 6, + TOKEN_LEFT_PAREN = 7, + TOKEN_RIGHT_PAREN = 8, + TOKEN_BANG = 9, + TOKEN_DOT = 10, + TOKEN_COMMA = 11, + }; + + /** + * Type of the token value. + */ + union Value + { + std::int32_t number; + const char *identifier; + }; + + Token(Type of, Position position); + Token(Type of, std::int32_t value, Position position); + Token(Type of, const char *value, Position position); + Token(const Token& that); + Token(Token&& that); + ~Token(); + + Token& operator=(const Token& that); + Token& operator=(Token&& that); + + Type of() const noexcept; + const char *identifier() const noexcept; + std::int32_t number() const noexcept; + const Position& position() const noexcept; + + private: + Type m_type; + Value m_value; + Position m_position; + }; + + /** + * Split the source into tokens. + * + * \return Tokens or error. + */ + Token *lex(const char *buffer, CompileError *compile_error, std::size_t *length); +} diff --git a/include/elna/parser.hpp b/include/elna/parser.hpp new file mode 100644 index 0000000..322277d --- /dev/null +++ b/include/elna/parser.hpp @@ -0,0 +1,10 @@ +#pragma once + +namespace elna +{ + enum class BinaryOperator + { + sum, + subtraction + }; +} diff --git a/include/elna/result.hpp b/include/elna/result.hpp new file mode 100644 index 0000000..72dc647 --- /dev/null +++ b/include/elna/result.hpp @@ -0,0 +1,62 @@ +#pragma once + +#include +#include + +namespace elna +{ + /** + * Position in the source text. + */ + struct Position + { + /// Line. + std::size_t line = 1; + + /// Column. + std::size_t column = 1; + }; + + /** + * A compilation error consists of an error message and position. + */ + struct CompileError + { + private: + char const *message; + Position position; + + public: + /** + * @param message Error text. + * @param position Error position in the source text. + */ + CompileError(char const *message, const Position position) noexcept; + + /// Error text. + const char *what() const noexcept; + + /// Error line in the source text. + std::size_t line() const noexcept; + + /// Error column in the source text. + std::size_t column() const noexcept; + }; + + template + using result = boost::outcome_v2::result; + + enum class Target + { + text, + high20, + lower12i + }; + + struct Reference + { + const char* name; + size_t offset; + Target target; + }; +} diff --git a/include/elna/riscv.hpp b/include/elna/riscv.hpp new file mode 100644 index 0000000..5a01bf9 --- /dev/null +++ b/include/elna/riscv.hpp @@ -0,0 +1,148 @@ +#pragma once + +#include +#include "elna/ir.hpp" +#include "elna/result.hpp" + +namespace elna +{ + enum class XRegister : std::uint8_t + { + zero = 0, + ra = 1, + sp = 2, + gp = 3, + tp = 4, + t0 = 5, + t1 = 6, + t2 = 7, + s0 = 8, + s1 = 9, + a0 = 10, + a1 = 11, + a2 = 12, + a3 = 13, + a4 = 14, + a5 = 15, + a6 = 16, + a7 = 17, + s2 = 18, + s3 = 19, + s4 = 20, + s5 = 21, + s6 = 22, + s7 = 23, + s8 = 24, + s9 = 25, + s10 = 26, + s11 = 27, + t3 = 28, + t4 = 29, + t5 = 30, + t6 = 31, + }; + + enum class Funct3 : std::uint8_t + { + addi = 0b000, + slti = 0b001, + sltiu = 0b011, + andi = 0b111, + ori = 0b110, + xori = 0b100, + slli = 0b000, + srli = 0b101, + srai = 0b101, + add = 0b000, + slt = 0b010, + sltu = 0b011, + _and = 0b111, + _or = 0b110, + _xor = 0b100, + sll = 0b001, + srl = 0b101, + sub = 0b000, + sra = 0b101, + beq = 0b000, + bne = 0b001, + blt = 0b100, + bltu = 0b110, + bge = 0b101, + bgeu = 0b111, + fence = 0b000, + fenceI = 0b001, + csrrw = 0b001, + csrrs = 0b010, + csrrc = 0b011, + csrrwi = 0b101, + csrrsi = 0b110, + csrrci = 0b111, + priv = 0b000, + sb = 0b000, + sh = 0b001, + sw = 0b010, + lb = 0b000, + lh = 0b001, + lw = 0b010, + lbu = 0b100, + lhu = 0b101, + jalr = 0b000, + }; + + enum class Funct12 : std::uint8_t + { + ecall = 0b000000000000, + ebreak = 0b000000000001, + }; + + enum class Funct7 : std::uint8_t + { + none = 0, + sub = 0b0100000 + }; + + enum class BaseOpcode : std::uint8_t + { + opImm = 0b0010011, + lui = 0b0110111, + auipc = 0b0010111, + op = 0b0110011, + jal = 0b1101111, + jalr = 0b1100111, + branch = 0b1100011, + load = 0b0000011, + store = 0b0100011, + miscMem = 0b0001111, + system = 0b1110011, + }; + + struct Instruction + { + Instruction(BaseOpcode opcode); + + Instruction& i(XRegister rd, Funct3 funct3, XRegister rs1, std::uint32_t immediate); + Instruction& s(std::uint32_t imm1, Funct3 funct3, XRegister rs1, XRegister rs2); + Instruction& r(XRegister rd, Funct3 funct3, XRegister rs1, XRegister rs2, Funct7 funct7 = Funct7::none); + Instruction& u(XRegister rd, std::uint32_t imm); + std::uint8_t *encode(); + + private: + std::uint32_t instruction{ 0 }; + }; + + class RiscVVisitor : public ir::IRVisitor + { + Instruction *instructions; + std::size_t instructionsLength; + bool registerInUse; + std::uint32_t variableCounter = 1; + Reference references[3]; + + virtual void visit(ir::Node *) override; + virtual void visit(ir::Definition *definition) override; + virtual void visit(ir::Operand *operand) override; + virtual void visit(ir::Variable *variable) override; + virtual void visit(ir::Number *number) override; + virtual void visit(ir::BinaryExpression *expression) override; + }; +} diff --git a/include/elna/state.hpp b/include/elna/state.hpp new file mode 100644 index 0000000..4c7e8f4 --- /dev/null +++ b/include/elna/state.hpp @@ -0,0 +1,170 @@ +#include +#include +#include +#include + +namespace elna +{ + constexpr const char *erase_line = "\x1b[2K"; + constexpr const char *start_kitty_keybaord = "\x1b[>1u"; + constexpr const char *end_kitty_keybaord = "\x1b[ store; + std::uint8_t modifiers; + }; + + /** + * The action should be performed after updating the editor state. + */ + enum class action + { + redraw, ///< Redraw everything. + write, ///< Write a single character. + finalize, ///< Finalize command input. + move, ///< Move the cursor. + ignore, ///< Do nothing. + complete, ///< Complete the input. + history, ///< Navigate the history. + }; + + /** + * The line editor with its state. + */ + class editor_state + { + std::string input; + std::string prompt = "> "; + std::size_t position{ 1 }; + + public: + editor_state(); + + /** + * Returns the current input gathered by the lin editor. + * + * \return User input. + */ + const std::string& command_line() const noexcept; + + /** + * Processes the next keypress by changing the line editor state. + * + * \param key Pressed key. + * \return Action to do after the key was pressed. + */ + action process_keypress(const key& press); + + /** + * Clears editors working area and writes drawing sequences into the + * buffer. + * + * \param T Output range for bytes. + * \param output Output buffer. + */ + template + void draw(T output) const + { + std::string code; + + std::copy(erase_line, erase_line + strlen(erase_line), output); + + code = cursor_to_column(); + std::copy(std::cbegin(code), std::cend(code), output); + + std::copy(std::cbegin(this->prompt), std::cend(this->prompt), output); + std::copy(std::cbegin(this->input), std::cend(this->input), output); + + code = cursor_to_column(absolute_position()); + std::copy(std::cbegin(code), std::cend(code), output); + } + + /** + * Computes the position in the terminal line. + * + * \return Position in the terminal line. + */ + std::size_t absolute_position() const noexcept; + + /** + * Cursor position relative to the start of the user input. + * + * \returns Cursor position. + */ + std::size_t relative_position() const noexcept; + + /** + * Inserts text at cursor position. + * + * \param text Text to insert. + */ + void insert(const std::string& text); + void insert(const key::char_t text); + + /** + * Replaces the user input with the given string. + * + * \param text New user input string. + */ + void load(const std::string& text); + }; +} diff --git a/include/elna/tester.hpp b/include/elna/tester.hpp new file mode 100644 index 0000000..ee34351 --- /dev/null +++ b/include/elna/tester.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include + +namespace elna +{ + 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; + std::uint32_t passed() const noexcept; + std::uint32_t failed() const noexcept; + + int exit_code() const noexcept; + void add_exit_code(const int exit_code) noexcept; + }; +} diff --git a/shell/history.cpp b/shell/history.cpp new file mode 100644 index 0000000..aad7375 --- /dev/null +++ b/shell/history.cpp @@ -0,0 +1,54 @@ +#include "elna/history.hpp" + +namespace elna +{ + editor_history::editor_history() + : commands{}, current_pointer(commands.cend()) + { + } + + void editor_history::push(const std::string& entry) + { + commands.push_back(entry); + current_pointer = commands.cend(); + } + + void editor_history::clear() + { + commands.clear(); + current_pointer = commands.cend(); + } + + editor_history::const_iterator editor_history::prev() noexcept + { + if (this->current_pointer != cbegin()) + { + this->current_pointer = std::prev(this->current_pointer); + } + return this->current_pointer; + } + + editor_history::const_iterator editor_history::next() noexcept + { + if (this->current_pointer != cend()) + { + this->current_pointer = std::next(this->current_pointer); + } + return this->current_pointer; + } + + editor_history::const_iterator editor_history::cbegin() const noexcept + { + return this->commands.cbegin(); + } + + editor_history::const_iterator editor_history::cend() const noexcept + { + return this->commands.cend(); + } + + editor_history::const_iterator editor_history::current() const noexcept + { + return this->current_pointer; + } +} diff --git a/shell/interactive.cpp b/shell/interactive.cpp new file mode 100644 index 0000000..e10446a --- /dev/null +++ b/shell/interactive.cpp @@ -0,0 +1,286 @@ +#include "elna/interactive.hpp" + +#include +#include +#include +#include + +namespace elna +{ + static termios original_termios; + + interactive_exception::interactive_exception() + : runtime_error("read") + { + } + + template + static std::pair read_number() + { + T position{ 0 }; + unsigned char c{ 0 }; + + while (read(STDIN_FILENO, &c, 1) == 1 && std::isdigit(c)) + { + position = position * 10 + (c - '0'); + } + return std::make_pair(position, c); + } + + void loop() + { + editor_history history; + do + { + auto line = read_line(history); + + if (!line.has_value()) + { + write(STDOUT_FILENO, "\r\n", 2); + break; + } + history.push(line.value().command_line()); + + if (!execute(line.value().command_line())) + { + break; + } + } + while (true); + } + + std::optional read_line(editor_history& history) + { + editor_state state; + std::string buffer; + std::string saved; + + state.draw(std::back_inserter(buffer)); + write(STDOUT_FILENO, buffer.data(), buffer.size()); + + while (true) + { + buffer.clear(); + auto pressed_key = read_key(); + + if (pressed_key.empty()) + { + continue; + } + switch (state.process_keypress(pressed_key)) + { + case action::finalize: + if (pressed_key == key('d', modifier::ctrl)) + { + return std::optional(); + } + else if (pressed_key == '\r') + { + write(STDOUT_FILENO, "\r\n", 2); + return std::make_optional(state); + } + break; + case action::complete: + write(STDOUT_FILENO, "\x1b[1E\x1b[2K", 8); + state.insert(complete(state.command_line(), state.relative_position())); + write(STDOUT_FILENO, "\x1b[1A", 4); + state.draw(std::back_inserter(buffer)); + write(STDOUT_FILENO, buffer.data(), buffer.size()); + break; + case action::redraw: + state.draw(std::back_inserter(buffer)); + write(STDOUT_FILENO, buffer.data(), buffer.size()); + break; + case action::write: + write(STDOUT_FILENO, &pressed_key, 1); + break; + case action::move: + buffer = cursor_to_column(state.absolute_position()); + write(STDOUT_FILENO, buffer.data(), buffer.size()); + break; + case action::ignore: + break; + case action::history: + editor_history::const_iterator history_iterator = history.cend(); + + if (pressed_key == key(special_key::arrow_up, modifier::ctrl)) + { + if (history.current() == history.cend()) + { + saved = state.command_line(); + } + history_iterator = history.prev(); + } + else if (pressed_key == key(special_key::arrow_down, modifier::ctrl)) + { + history_iterator = history.next(); + if (history_iterator == history.cend()) + { + state.load(saved); + } + } + if (history_iterator != history.cend()) + { + state.load(*history_iterator); + } + state.draw(std::back_inserter(buffer)); + write(STDOUT_FILENO, buffer.data(), buffer.size()); + break; + } + } + } + + key read_key() + { + char c{ 0 }; + + if (read(STDIN_FILENO, &c, 1) == -1 && errno != EAGAIN) + { + throw interactive_exception(); + } + if (c != '\x1b') + { + return key(c); + } + if (read(STDIN_FILENO, &c, 1) != 1 || c != '[') + { + return key(); + } + auto [number, last_char] = read_number(); + std::uint8_t modifiers{ 0 }; + + if (last_char == ';') + { + auto modifier_response = read_number(); + modifiers = modifier_response.first; + last_char = modifier_response.second; + } + if (number == 0 || number == 1) + { + switch (last_char) + { + case 'A': + return key(special_key::arrow_up, modifiers); + case 'B': + return key(special_key::arrow_down, modifiers); + case 'C': + return key(special_key::arrow_right, modifiers); + case 'D': + return key(special_key::arrow_left, modifiers); + case 'H': + return key(special_key::home_key, modifiers); + case 'F': + return key(special_key::end_key, modifiers); + } + } + else if (last_char == '~') + { + switch (number) + { + case 5: + return key(special_key::page_up, modifiers); + case 6: + return key(special_key::page_up, modifiers); + case 7: + return key(special_key::home_key, modifiers); + case 8: + return key(special_key::end_key, modifiers); + } + } + else if (last_char == 'u') + { + return key(number, modifiers); + } + return key(); + } + + void print_exception(const std::exception& exception) + { + std::string message{ exception.what() }; + message += "\r\n"; + write(STDERR_FILENO, message.data(), message.size()); + } + + bool execute(const std::string& line) + { + if (line.empty()) + { + return true; + } + if (line == "exit") + { + return false; + } + else if (boost::starts_with(line, "cd ")) + { + try + { + std::filesystem::current_path(line.substr(strlen("cd "))); + } + catch (const std::filesystem::filesystem_error& exception) + { + print_exception(exception); + } + + return true; + } + launch(line); + return true; + } + + void launch(const std::string& program) + { + try + { + boost::process::system(program); + } + catch (const boost::process::process_error& exception) + { + print_exception(exception); + } + enable_raw_mode(); + } + + bool enable_raw_mode() + { + if (tcgetattr(STDIN_FILENO, &original_termios) == -1) + { + return false; + } + termios raw = original_termios; + + raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON); + raw.c_oflag &= ~(OPOST); + raw.c_cflag |= CS8; + raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG); + + write(STDOUT_FILENO, start_kitty_keybaord, strlen(start_kitty_keybaord)); + + return tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw) != -1; + } + + void disable_raw_mode() + { + write(STDOUT_FILENO, end_kitty_keybaord, strlen(end_kitty_keybaord)); + tcsetattr(STDIN_FILENO, TCSAFLUSH, &original_termios); + } + + std::string complete(const std::string& input, const std::size_t position) + { + std::filesystem::path program = boost::process::search_path("fzf"); + + if (program.empty()) + { + return ""; + } + boost::process::ipstream output; + boost::process::system(program, + "--height=10", "--layout=reverse", "-1", "--no-multi", + boost::process::std_out > output); + + std::string selections; + std::getline(output, selections, '\n'); + + return selections; + } +} diff --git a/shell/main.cpp b/shell/main.cpp new file mode 100644 index 0000000..19b1892 --- /dev/null +++ b/shell/main.cpp @@ -0,0 +1,16 @@ +#include +#include +#include "elna/interactive.hpp" + +int main() +{ + if (!elna::enable_raw_mode()) + { + std::perror("tcsetattr"); + return EXIT_FAILURE; + } + std::atexit(elna::disable_raw_mode); + elna::loop(); + + return EXIT_SUCCESS; +} diff --git a/shell/state.cpp b/shell/state.cpp new file mode 100644 index 0000000..f148980 --- /dev/null +++ b/shell/state.cpp @@ -0,0 +1,254 @@ +#include +#include + +#include "elna/state.hpp" + +namespace elna +{ + std::string cursor_to_column(const std::uint16_t position) + { + std::string code = std::to_string(position); + code.insert(0, "\x1b["); + code.push_back('G'); + + return code; + } + + key::key() + : store(0x1bu) + { + } + + key::key(const char_t c, const std::uint8_t modifiers) + : store(c), modifiers(modifiers == 0 ? 0 : modifiers - 1) + { + } + + key::key(const char_t c, const modifier modifiers) + : store(c), modifiers(static_cast(modifiers)) + { + } + + key::key(const special_key control, const std::uint8_t modifiers) + : store(control), modifiers(modifiers == 0 ? 0 : modifiers - 1) + { + } + + key::key(const special_key control, const modifier modifiers) + : store(control), modifiers(static_cast(modifiers)) + { + } + + key::char_t key::character() const + { + return std::get(this->store); + } + + special_key key::control() const + { + return std::get(this->store); + } + + bool key::modified(const std::uint8_t modifiers) const noexcept + { + return this->modifiers == modifiers; + } + + bool key::modified(const modifier modifiers) const noexcept + { + return this->modifiers == static_cast(modifiers); + } + + bool key::operator==(const char_t c) const + { + return std::holds_alternative(this->store) + && std::get(this->store) == c; + } + + bool key::operator==(const special_key control) const + { + return std::holds_alternative(this->store) + && std::get(this->store) == control; + } + + bool key::operator==(const key& that) const + { + return this->store == that.store; + } + + bool key::operator!=(const char_t c) const + { + return !(*this == c); + } + + bool key::operator!=(const special_key control) const + { + return !(*this == control); + } + + bool key::operator!=(const key& that) const + { + return !(*this == that); + } + + bool key::is_character() const noexcept + { + return std::holds_alternative(this->store); + } + + bool key::is_control() const noexcept + { + return std::holds_alternative(this->store); + } + + bool key::empty() const noexcept + { + return *this == '\x1b'; + } + + bool operator==(const special_key control, const key& that) + { + return that == control; + } + + bool operator==(const char c, const key& that) + { + return that == c; + } + + bool operator!=(const special_key control, const key& that) + { + return that != control; + } + + bool operator!=(const char c, const key& that) + { + return that != c; + } + + editor_state::editor_state() + { + this->input.reserve(1024); + } + + void editor_state::load(const std::string& text) + { + this->input = text; + this->position = this->input.size() + 1; + } + + const std::string& editor_state::command_line() const noexcept + { + return this->input; + } + + std::size_t editor_state::absolute_position() const noexcept + { + return this->prompt.size() + this->position; + } + + std::size_t editor_state::relative_position() const noexcept + { + return this->position; + } + + void editor_state::insert(const std::string& text) + { + this->input.insert(this->position - 1, text); + this->position += text.size(); + } + + void editor_state::insert(const key::char_t text) + { + if (text == 0u) + { + this->input.insert(this->position - 1, '\0', 1); + ++this->position; + return; + } + key::char_t buffer = boost::endian::native_to_big(text); + + const char *begin = reinterpret_cast(&buffer); + const char *end = begin + sizeof(key::char_t); + const char *significant = std::find_if(begin, end, + [](const char byte) { + return byte != 0; + }); + + this->input.insert(this->position - 1, significant, end - significant); + this->position += end - significant; + } + + action editor_state::process_keypress(const key& press) + { + if (press.is_control()) + { + switch (press.control()) + { + case special_key::arrow_left: + if (this->position > 1) + { + --this->position; + } + return action::move; + case special_key::arrow_right: + if (this->position + 1 < this->input.size() + this->prompt.size()) + { + ++this->position; + } + return action::move; + case special_key::arrow_up: + case special_key::arrow_down: + return action::history; + case special_key::home_key: + this->position = 1; + return action::move; + case special_key::end_key: + this->position = this->input.size() + 1; + return action::move; + default: + return action::ignore; + } + } + else if (press.modified(modifier::ctrl)) + { + switch (press.character()) + { + case 'd': + return action::finalize; + case 't': + return action::complete; + default: + return action::ignore; + } + } + else + { + switch (press.character()) + { + case 127: // Backspace. + if (this->position <= this->input.size() + 1 && this->position > 1) + { + --this->position; + this->input.erase(this->position - 1, 1); + } + return action::redraw; + case 9: // Tab. + return action::complete; + case '\r': + return action::finalize; + default: + if (this->position - 1 < input.size()) + { + insert(press.character()); + return action::redraw; + } + else + { + insert(press.character()); + return action::write; + } + } + } + } +} diff --git a/source/elna/arguments.d b/source/elna/arguments.d new file mode 100644 index 0000000..1ad928a --- /dev/null +++ b/source/elna/arguments.d @@ -0,0 +1,154 @@ +/** + * Argument parsing. + */ +module elna.arguments; + +import std.algorithm; +import std.range; +import std.sumtype; + +struct ArgumentError +{ + enum Type + { + expectedOutputFile, + noInput, + superfluousArguments, + } + + private Type type_; + private string argument_; + + @property Type type() const @nogc nothrow pure @safe + { + return this.type_; + } + + @property string argument() const @nogc nothrow pure @safe + { + return this.argument_; + } + + void toString(OR)(OR range) + if (isOutputRage!OR) + { + final switch (Type) + { + case Type.expectedOutputFile: + put(range, "Expected an output filename after -o"); + break; + case Type.noInput: + put(range, "No input files specified"); + break; + } + } +} + +/** + * Supported compiler arguments. + */ +struct Arguments +{ + private bool assembler_; + private string output_; + private string inFile_; + + @property string inFile() @nogc nothrow pure @safe + { + return this.inFile_; + } + + /** + * Returns: Whether to generate assembly instead of an object file. + */ + @property bool assembler() const @nogc nothrow pure @safe + { + return this.assembler_; + } + + /** + * Returns: Output file. + */ + @property string output() const @nogc nothrow pure @safe + { + return this.output_; + } + + /** + * Parse command line arguments. + * + * The first argument is expected to be the program name (and it is + * ignored). + * + * Params: + * arguments = Command line arguments. + * + * Returns: Parsed arguments or an error. + */ + static SumType!(ArgumentError, Arguments) parse(string[] arguments) + @nogc nothrow pure @safe + { + if (!arguments.empty) + { + arguments.popFront; + } + alias ReturnType = typeof(return); + + return parseArguments(arguments).match!( + (Arguments parsed) { + if (parsed.inFile is null) + { + return ReturnType(ArgumentError(ArgumentError.Type.noInput)); + } + else if (!arguments.empty) + { + return ReturnType(ArgumentError( + ArgumentError.Type.superfluousArguments, + arguments.front + )); + } + return ReturnType(parsed); + }, + (ArgumentError argumentError) => ReturnType(argumentError) + ); + } + + private static SumType!(ArgumentError, Arguments) parseArguments(ref string[] arguments) + @nogc nothrow pure @safe + { + Arguments parsed; + + while (!arguments.empty) + { + if (arguments.front == "-s") + { + parsed.assembler_ = true; + } + else if (arguments.front == "-o") + { + if (arguments.empty) + { + return typeof(return)(ArgumentError( + ArgumentError.Type.expectedOutputFile, + arguments.front + )); + } + arguments.popFront; + parsed.output_ = arguments.front; + } + else if (arguments.front == "--") + { + arguments.popFront; + parsed.inFile_ = arguments.front; + arguments.popFront; + break; + } + else if (!arguments.front.startsWith("-")) + { + parsed.inFile_ = arguments.front; + } + arguments.popFront; + } + return typeof(return)(parsed); + } +} diff --git a/source/elna/backend.d b/source/elna/backend.d new file mode 100644 index 0000000..e2d67ac --- /dev/null +++ b/source/elna/backend.d @@ -0,0 +1,114 @@ +module elna.backend; + +import core.stdc.stdio; +import core.stdc.stdlib; +import core.stdc.string; +import elna.elf; +import elna.ir; +import elna.extended; +import elna.riscv; +import elna.lexer; +import elna.parser; +import elna.result; +import std.algorithm; +import std.sumtype; +import std.typecons; +import tanya.os.error; +import tanya.container.array; +import tanya.container.string; +import tanya.memory.allocator; + +private char* readSource(string source) @nogc +{ + enum size_t bufferSize = 255; + auto sourceFilename = String(source); + + return readFile(sourceFilename).match!( + (ErrorCode errorCode) { + perror(sourceFilename.toStringz); + return null; + }, + (Array!ubyte contents) { + char* cString = cast(char*) malloc(contents.length + 1); + memcpy(cString, contents.get.ptr, contents.length); + cString[contents.length] = '\0'; + + return cString; + } + ); +} + +int generate(string inFile, ref String outputFilename) @nogc +{ + auto sourceText = readSource(inFile); + if (sourceText is null) + { + return 3; + } + CompileError compileError = void; + size_t tokensCount; + auto tokens = lex(sourceText, &compileError, &tokensCount); + free(sourceText); + if (tokens is null) + { + printf("%lu:%lu: %s\n", compileError.line, compileError.column, compileError.what); + return 1; + } + auto ast = parse(tokens[0 .. tokensCount]); + if (!ast.valid) + { + compileError = ast.error.get; + printf("%lu:%lu: %s\n", compileError.line, compileError.column, compileError.what); + return 2; + } + auto transformVisitor = cast(TransformVisitor) malloc(__traits(classInstanceSize, TransformVisitor)); + (cast(void*) transformVisitor)[0 .. __traits(classInstanceSize, TransformVisitor)] = __traits(initSymbol, TransformVisitor)[]; + + auto ir = transformVisitor.visit(ast.result); + + transformVisitor.__xdtor(); + free(cast(void*) transformVisitor); + + auto handle = File.open(outputFilename.toStringz, BitFlags!(File.Mode)(File.Mode.truncate)); + if (!handle.valid) + { + return 1; + } + auto program = writeNext(ir); + auto elf = Elf!ELFCLASS32(move(handle)); + auto readOnlyData = Array!ubyte(cast(const(ubyte)[]) "%d\n".ptr[0 .. 4]); // With \0. + + elf.addReadOnlyData(String(".CL0"), readOnlyData); + elf.addCode(program.name, program.text); + + elf.addExternSymbol(String("printf")); + foreach (ref reference; program.symbols) + { + elf.Rela relocationEntry = { + r_offset: cast(elf.Addr) reference.offset + }; + elf.Rela relocationSub = { + r_offset: cast(elf.Addr) reference.offset, + r_info: R_RISCV_RELAX + }; + + final switch (reference.target) + { + case Target.text: + relocationEntry.r_info = R_RISCV_CALL; + break; + case Target.high20: + relocationEntry.r_info = R_RISCV_HI20; + break; + case Target.lower12i: + relocationEntry.r_info = R_RISCV_LO12_I; + break; + } + + elf.relocate(String(reference.name[0 .. strlen(reference.name)]), relocationEntry, relocationSub); + } + + elf.finish(); + + return 0; +} diff --git a/source/elna/elf.d b/source/elna/elf.d new file mode 100644 index 0000000..81dd7da --- /dev/null +++ b/source/elna/elf.d @@ -0,0 +1,1060 @@ +module elna.elf; + +import elna.extended; +import elna.result; +import std.algorithm; +import tanya.container.array; +import tanya.container.hashtable; +import tanya.container.string; + +/// Unsigned program address. +alias Elf64_Addr = ulong; +/// Unsigned file offset. +alias Elf64_Off = ulong; +/// Unsigned medium integer. +alias Elf64_Half = ushort; +/// Unsigned integer. +alias Elf64_Word = uint; +/// Signed integer. +alias Elf64_Sword = int; +/// Unsigned long integer. +alias Elf64_Xword = ulong; +/// Signed long integer. +alias Elf64_Sxword = long; + +/// Unsigned program address. +alias Elf32_Addr = uint; +/// Unsigned file offset. +alias Elf32_Off = uint; +/// Unsigned medium integer. +alias Elf32_Half = ushort; +/// Unsigned integer. +alias Elf32_Word = uint; +/// Signed integer. +alias Elf32_Sword = int; + +enum : size_t +{ + /// File identification. + EI_MAG0 = 0, + /// File identification. + EI_MAG1 = 1, + /// File identification. + EI_MAG2 = 2, + /// File identification. + EI_MAG3 = 3, + /// File class. + EI_CLASS = 4, + /// Data encoding. + EI_DATA = 5, + /// File version. + EI_VERSION = 6, + /// Start of padding bytes. + EI_PAD = 7, + /// Size of e_ident[] + EI_NIDENT = 16 +} + +enum : ubyte +{ + /// e_ident[EI_MAG0]. + ELFMAG0 = 0x7f, + /// e_ident[EI_MAG1]. + ELFMAG1 = 'E', + /// e_ident[EI_MAG2]. + ELFMAG2 = 'L', + /// e_ident[EI_MAG3]. + ELFMAG3 = 'F' +} + +/** + * File header. + */ +struct Elf64_Ehdr +{ + /// ELF identification. + ubyte[EI_NIDENT] e_ident; + /// Object file type. + Elf64_Half e_type; + /// Machine type. + Elf64_Half e_machine; + /// Object file version + Elf64_Word e_version; + /// Entry point address. + Elf64_Addr e_entry; + /// Program header offset. + Elf64_Off e_phoff; + /// Section header offset. + Elf64_Off e_shoff; + /// Processor-specific flags. + Elf64_Word e_flags; + /// ELF header size. + Elf64_Half e_ehsize; + /// Size of program header entry. + Elf64_Half e_phentsize; + /// Number of program header entries. + Elf64_Half e_phnum; + /// Size of section header entry. + Elf64_Half e_shentsize; + /// Number of section header entries. + Elf64_Half e_shnum; + /// Section name string table index. + Elf64_Half e_shstrndx; +} + +/** + * File header. + */ +struct Elf32_Ehdr { + /// ELF identification. + ubyte[EI_NIDENT] e_ident; + /// Object file type. + Elf32_Half e_type; + /// Machine type. + Elf32_Half e_machine; + /// Object file version + Elf32_Word e_version; + /// Entry point address. + Elf32_Addr e_entry; + /// Program header offset. + Elf32_Off e_phoff; + /// Section header offset. + Elf32_Off e_shoff; + /// Processor-specific flags. + Elf32_Word e_flags; + /// ELF header size. + Elf32_Half e_ehsize; + /// Size of program header entry. + Elf32_Half e_phentsize; + /// Number of program header entries. + Elf32_Half e_phnum; + /// Size of section header entry. + Elf32_Half e_shentsize; + /// Number of section header entries. + Elf32_Half e_shnum; + /// Section name string table index. + Elf32_Half e_shstrndx; +} + +/** + * Section header. + */ +struct Elf64_Shdr +{ + /// Section name. + Elf64_Word sh_name; + /// Section type. + Elf64_Word sh_type; + /// Section attributes. + Elf64_Xword sh_flags; + /// Virtual address in memory. + Elf64_Addr sh_addr; + /// Offset in file. + Elf64_Off sh_offset; + /// Size of section. + Elf64_Xword sh_size; + /// Link to other section. + Elf64_Word sh_link; + /// Miscellaneous information. + Elf64_Word sh_info; + /// Address alignment boundary. + Elf64_Xword sh_addralign; + /// Size of entries, if section has table. + Elf64_Xword sh_entsize; +} + +/** + * Section header. + */ +struct Elf32_Shdr +{ + /// Section name. + Elf32_Word sh_name; + /// Section type. + Elf32_Word sh_type; + /// Section attributes. + Elf32_Word sh_flags; + /// Virtual address in memory. + Elf32_Addr sh_addr; + /// Offset in file. + Elf32_Off sh_offset; + /// Size of section. + Elf32_Word sh_size; + /// Link to other section. + Elf32_Word sh_link; + /// Miscellaneous information. + Elf32_Word sh_info; + /// Address alignment boundary. + Elf32_Word sh_addralign; + /// Size of entries, if section has table. + Elf32_Word sh_entsize; +} + +/** + * Symbol table entry. + */ +struct Elf64_Sym +{ + /// Symbol name. + Elf64_Word st_name; + /// Type and Binding attributes. + ubyte st_info; + /// Reserved. + ubyte st_other; + /// Section table index. + Elf64_Half st_shndx; + /// Symbol value. + Elf64_Addr st_value; + /// Size of object (e.g., common). + Elf64_Xword st_size; +} + +/** + * Relocation entry. + */ +struct Elf64_Rel +{ + /// Address of reference. + Elf64_Addr r_offset; + /// Symbol index and type of relocation. + Elf64_Xword r_info; +} + +/** + * Relocation entry with explicit addend. + */ +struct Elf64_Rela +{ + /// Address of reference. + Elf64_Addr r_offset; + /// Symbol index and type of relocation. + Elf64_Xword r_info; + /// Constant part of expression. + Elf64_Sxword r_addend; +} + +/** + * Symbol table entry. + */ +struct Elf32_Sym +{ + /// Symbol name. + Elf32_Word st_name; + /// Symbol value. + Elf32_Addr st_value; + /// Size of object (e.g., common). + Elf32_Word st_size; + /// Type and Binding attributes. + ubyte st_info; + /// Reserved. + ubyte st_other; + /// Section table index. + Elf32_Half st_shndx; +} + +/** + * Relocation entry. + */ +struct Elf32_Rel +{ + /// Address of reference. + Elf32_Addr r_offset; + /// Symbol index and type of relocation. + Elf32_Word r_info; +} + +/** + * Relocation entry with explicit addend. + */ +struct Elf32_Rela +{ + /// Address of reference. + Elf32_Addr r_offset; + /// Symbol index and type of relocation. + Elf32_Word r_info; + /// Constant part of expression. + Elf32_Sword r_addend; +} + +/// Section Types, sh_type. +enum : Elf64_Word +{ + /// Marks an unused section header. + SHT_NULL = 0, + /// Contains information defined by the program. + SHT_PROGBITS = 1, + /// Contains a linker symbol table. + SHT_SYMTAB = 2, + /// Contains a string table. + SHT_STRTAB = 3, + /// Contains “Rela” type relocation entries. + SHT_RELA = 4, + /// Contains a symbol hash table + SHT_HASH = 5, + /// Contains dynamic linking tables + SHT_DYNAMIC = 6, + /// Contains note information + SHT_NOTE = 7, + /// Contains uninitialized space; does not occupy any space in the file. + SHT_NOBITS = 8, + /// Contains "Rel" type relocation entries. + SHT_REL = 9, + /// Reserved. + SHT_SHLIB = 10, + /// Contains a dynamic loader symbol table. + SHT_DYNSYM = 11, + /// Environment-specific use. + SHT_LOOS = 0x60000000, + SHT_HIOS = 0x6FFFFFFF, + /// Processor-specific use. + SHT_LOPROC = 0x70000000, + SHT_HIPROC = 0x7FFFFFFF, +} + +/** + * Section Attributes, sh_flags. + */ +enum : Elf64_Xword +{ + /// Section contains writable data. + SHF_WRITE = 0x1, + /// Section is allocated in memory image of program. + SHF_ALLOC = 0x2, + /// Section contains executable instructions. + SHF_EXECINSTR = 0x4, + /// Environment-specific use. + SHF_MASKOS = 0x0F000000, + /// Processor-specific use. + SHF_MASKPROC = 0xF0000000, +} + +ubyte ELF64_R_SYM(Elf64_Xword i) @nogc nothrow pure @safe +{ + return cast(ubyte) (i >> 32); +} + +Elf64_Xword ELF64_R_TYPE(Elf64_Xword i) @nogc nothrow pure @safe +{ + return i & 0xffffffffL; +} + +Elf64_Xword ELF64_R_INFO(Elf64_Xword s, Elf64_Xword t) @nogc nothrow pure @safe +{ + return (s << 32) + (t & 0xffffffffL); +} + +ubyte ELF32_ST_BIND(ubyte i) @nogc nothrow pure @safe +{ + return i >> 4; +} + +ubyte ELF32_ST_TYPE(ubyte i) @nogc nothrow pure @safe +{ + return i & 0xf; +} + +ubyte ELF32_ST_INFO(Elf32_Word b, ubyte t) @nogc nothrow pure @safe +{ + return cast(ubyte) ((b << 4) + (t & 0xf)); +} + +Elf32_Word ELF32_R_SYM(Elf32_Word i) @nogc nothrow pure @safe +{ + return i >> 8; +} + +ubyte ELF32_R_TYPE(Elf32_Word i) @nogc nothrow pure @safe +{ + return cast(ubyte) i; +} + +Elf32_Word ELF32_R_INFO(Elf32_Word s, Elf32_Word t) @nogc nothrow pure @safe +{ + return (s << 8) + t; +} + +enum : uint +{ + /// Not visible outside the object file. + STB_LOCAL = 0, + /// Global symbol, visible to all object files. + STB_GLOBAL = 1, + /// Global scope, but with lower precedence than global symbols. + STB_WEAK = 2, + /// Environment-specific use. + STB_LOOS = 10, + STB_HIOS = 12, + /// Processor-specific use. + STB_LOPROC = 13, + STB_HIPROC = 15, +} + +enum : uint +{ + /// No type specified (e.g., an absolute symbol). + STT_NOTYPE = 0, + /// Data object. + STT_OBJECT = 1, + /// Function entry point. + STT_FUNC = 2, + /// Symbol is associated with a section. + STT_SECTION = 3, + /// Source file associated with the object file. + STT_FILE = 4, + /// Environment-specific use. + STT_LOOS = 10, + STT_HIOS = 12, + /// Processor-specific use. + STT_LOPROC = 13, + STT_HIPROC = 15, +} + +/// Special Section Indices. +enum : ushort +{ + /// Used to mark an undefined or meaningless section reference. + SHN_UNDEF = 0, + /// This value specifies the lower bound of the range of reserved indexes. + SHN_LORESERVE = 0xff00, + /// Processor-specific use. + SHN_LOPROC = 0xFF00, + SHN_HIPROC = 0xFF1F, + /// Environment-specific use. + SHN_LOOS = 0xFF20, + SHN_HIOS = 0xFF3F, + /// Indicates that the corresponding reference is an absolute value. + SHN_ABS = 0xFFF1, + /** + * Indicates a symbol that has been declared as a common block (Fortran + * COMMON or C tentative declaration). + */ + SHN_COMMON = 0xFFF2, +} + +/** + * Object File Classes, e_ident[EI_CLASS]. + */ +enum : ubyte +{ + /// Invalid class. + ELFCLASSNONE = 0, + /// 32-bit objects. + ELFCLASS32 = 1, + /// 64-bit objects. + ELFCLASS64 = 2 +} + +enum : ubyte { + /// Invalid version. + EV_NONE = 0, + /// Current version. + EV_CURRENT = 1 +} + +/** + * Data Encodings, e_ident[EI_DATA]. + */ +enum : ubyte +{ + /// Object file data structures are little-endian. + ELFDATA2LSB = 1, + /// Object file data structures are big-endian. + ELFDATA2MSB = 2, +} + +/** + * Operating System and ABI Identifiers, e_ident[EI_OSABI]. + */ +enum EI_OSABI : ubyte +{ + /// System V ABI. + ELFOSABI_SYSV = 0, + /// HP-UX operating system. + ELFOSABI_HPUX = 1, + /// Standalone (embedded) application. + ELFOSABI_STANDALONE = 255, +} + +enum : Elf64_Half +{ + ET_NONE = 0, /// No file type. + ET_REL = 1, /// Relocatable object file. + ET_EXEC = 2, /// Executable file. + ET_DYN = 3, /// Shared object file. + ET_CORE = 4, /// Core file. + ET_LOOS = 0xFE00, /// Environment-specific use. + ET_HIOS = 0xFEFF, + ET_LOPROC = 0xFF00, /// Processor-specific use. + ET_HIPROC = 0xFFFF, +} + +enum : ubyte +{ + R_RISCV_NONE = 0, + /// 32-bit relocation. + R_RISCV_32 = 1, + /// 64-bit relocation. + R_RISCV_64 = 2, + /// Relocation against a local symbol in a shared object. + R_RISCV_RELATIVE = 3, + /// Must be in executable; not allowed in shared library. + R_RISCV_COPY = 4, + /// Indicates the symbol associated with a PLT entry. + R_RISCV_JUMP_SLOT = 5, + R_RISCV_TLS_DTPMOD32 = 6, + R_RISCV_TLS_DTPMOD64 = 7, + R_RISCV_TLS_DTPREL32 = 8, + R_RISCV_TLS_DTPREL64 = 9, + R_RISCV_TLS_TPREL32 = 10, + R_RISCV_TLS_TPREL64 = 11, + /// 12-bit PC-relative branch offset. + R_RISCV_BRANCH = 16, + /// 20-bit PC-relative jump offset. + R_RISCV_JAL = 17, + /// 32-bit PC-relative function call, macros `call`, `tail`. + R_RISCV_CALL = 18, + /// 32-bit PC-relative function call, macros `call`, `tail` (PIC). + R_RISCV_CALL_PLT = 19, + /// High 20 bits of 32-bit PC-relative GOT access, `%got_pcrel_hi(symbol)`. + R_RISCV_GOT_HI20 = 20, + /// High 20 bits of 32-bit PC-relative TLS IE GOT access, macro `la.tls.ie`. + R_RISCV_TLS_GOT_HI20 = 21, + /// High 20 bits of 32-bit PC-relative TLS GD GOT reference, macro `la.tls.gd`. + R_RISCV_TLS_GD_HI20 = 22, + /// High 20 bits of 32-bit PC-relative reference, `%pcrel_hi(symbol)`. + R_RISCV_PCREL_HI20 = 23, + /// Low 12 bits of a 32-bit PC-relative, `%pcrel_lo(address of %pcrel_hi)`, the addend must be 0. + R_RISCV_PCREL_LO12_I = 24, + /// Low 12 bits of a 32-bit PC-relative, `%pcrel_lo(address of %pcrel_hi)`, the addend must be 0. + R_RISCV_PCREL_LO12_S = 25, + /// High 20 bits of 32-bit absolute address, `%hi(symbol)`. + R_RISCV_HI20 = 26, + /// Low 12 bits of 32-bit absolute address, `%lo(symbol)`. + R_RISCV_LO12_I = 27, + /// Low 12 bits of 32-bit absolute address, `%lo(symbol)`. + R_RISCV_LO12_S = 28, + /// High 20 bits of TLS LE thread pointer offset, `%tprel_hi(symbol)`. + R_RISCV_TPREL_HI20 = 29, + /// Low 12 bits of TLS LE thread pointer offset, `%tprel_lo(symbol)`. + R_RISCV_TPREL_LO12_I = 30, + /// Low 12 bits of TLS LE thread pointer offset, `%tprel_lo(symbol)`. + R_RISCV_TPREL_LO12_S = 31, + /// TLS LE thread pointer usage, `%tprel_add(symbol)`. + R_RISCV_TPREL_ADD = 32, + /// 8-bit label addition. + R_RISCV_ADD8 = 33, + /// 16-bit label addition. + R_RISCV_ADD16 = 34, + /// 32-bit label addition. + R_RISCV_ADD32 = 35, + /// 64-bit label addition. + R_RISCV_ADD64 = 36, + /// 8-bit label subtraction. + R_RISCV_SUB8 = 37, + /// 16-bit label subtraction. + R_RISCV_SUB16 = 38, + /// 32-bit label subtraction. + R_RISCV_SUB32 = 39, + /// 64-bit label subtraction. + R_RISCV_SUB64 = 40, + /// GNU {Cpp} vtable hierarchy. + R_RISCV_GNU_VTINHERIT = 41, + /// GNU {Cpp} vtable member usage. + R_RISCV_GNU_VTENTRY = 42, + /// Alignment statement. + R_RISCV_ALIGN = 43, + /// 8-bit PC-relative branch offset. + R_RISCV_RVC_BRANCH = 44, + /// 11-bit PC-relative jump offset. + R_RISCV_RVC_JUMP = 45, + /// High 6 bits of 18-bit absolute address. + R_RISCV_RVC_LUI = 46, + /// Instruction can be relaxed, paired with a normal relocation at the same address. + R_RISCV_RELAX = 51, + /// Local label subtraction. + R_RISCV_SUB6 = 52, + /// Local label assignment. + R_RISCV_SET6 = 53, + /// Local label assignment. + R_RISCV_SET8 = 54, + /// Local label assignment. + R_RISCV_SET16 = 55, + /// Local label assignment. + R_RISCV_SET32 = 56, + /// 32-bit PC relative. + R_RISCV_32_PCREL = 57, + /// Relocation against a local ifunc symbol in a shared object. + R_RISCV_IRELATIVE = 58 +} + +auto pad(ubyte elfClass)(size_t value) @nogc +{ + static if (elfClass == ELFCLASS32) + { + return cast(Elf32_Word) (value / 4 + 1) * 4; + } + else static if (elfClass == ELFCLASS64) + { + return cast(Elf64_Xword) (value / 8 + 1) * 8; + } + else + { + static assert(false, "Invalid ELF class"); + } +} + +private struct Relocation(Sym, Rel) +{ + Sym symbol; + Array!Rel relocations; +} + +struct Elf(ubyte elfClass) +{ + static if (elfClass == ELFCLASS32) + { + alias Addr = Elf32_Addr; + alias Off = Elf32_Off; + alias Half = Elf32_Half; + alias Word = Elf32_Word; + alias Sword = Elf32_Sword; + alias Xword = Elf32_Word; + alias Sxword = Elf32_Sword; + + alias Ehdr = Elf32_Ehdr; + alias Shdr = Elf32_Shdr; + alias Rel = Elf32_Rel; + alias Rela = Elf32_Rela; + alias Sym = Elf32_Sym; + + alias R_SYM = ELF32_R_SYM; + alias R_TYPE = ELF32_R_TYPE; + alias R_INFO = ELF32_R_INFO; + alias ST_BIND = ELF32_ST_BIND; + alias ST_TYPE = ELF32_ST_TYPE; + alias ST_INFO = ELF32_ST_INFO; + } + else static if (elfClass == ELFCLASS64) + { + alias Addr = Elf64_Addr; + alias Off = Elf64_Off; + alias Half = Elf64_Half; + alias Word = Elf64_Word; + alias Sword = Elf64_Sword; + alias Xword = Elf64_Xword; + alias Sxword = Elf64_Sxword; + + alias Ehdr = Elf64_Ehdr; + alias Shdr = Elf64_Shdr; + alias Rel = Elf64_Rel; + alias Rela = Elf64_Rela; + alias Sym = Elf64_Sym; + + alias R_SYM = ELF64_R_SYM; + alias R_TYPE = ELF64_R_TYPE; + alias R_INFO = ELF64_R_INFO; + alias ST_BIND = ELF64_ST_BIND; + alias ST_TYPE = ELF64_ST_TYPE; + alias ST_INFO = ELF64_ST_INFO; + } + else + { + static assert(false, "Invalid ELF class"); + } + + private alias Relocation = .Relocation!(Sym, Rela); + + private Array!Shdr sectionHeaders; + private Off currentOffset = Elf32_Ehdr.sizeof; + static immutable char[52] sections = + "\0.symtab\0.strtab\0.shstrtab\0.text\0.rodata\0.rela.text\0"; + private String strings; + private File output; + private Array!ubyte readOnly; + + private HashTable!(String, Relocation) symbolTable; + + private enum HeaderName + { + text = 0x1b, + roData = 0x21, + string_ = 0x09, + headerString = 0x11, + symbol = 0x01, + rela = 0x29 + } + + static Elf opCall(File output) @nogc + { + Elf elf = Elf.init; + + elf.initializeSectionHeaders(); + elf.output = move(output); + + elf.output.seek(Ehdr.sizeof, File.Whence.set); + + elf.makeTextHeader(); + elf.makeRoDataHeader(); + elf.makeSymbolHeader(); + elf.makeRelaHeader(); + elf.makeStringHeader!(HeaderName.string_)(); + elf.makeStringHeader!(HeaderName.headerString)(); + + return elf; + } + + @disable this(this); + + void finish() @nogc + { + writeRoDataTable(); + writeSymbolTable(); + writeStringTables(); + + // End writing data, start writing headers. + + output.write((cast(ubyte*) this.sectionHeaders.get)[0 .. Shdr.sizeof * this.sectionHeaders.length]); + + writeFileHeader(); + } + + private Sym initializeSymbols() @nogc + { + // Zero symbol + Sym symbol; + symbol.st_name = 0; // Word + symbol.st_value = 0; // Addr + symbol.st_size = 0; // Word + symbol.st_info = 0; // char + symbol.st_other = 0; // char + symbol.st_shndx = 0; // Half word + + return symbol; + } + + private void makeStringHeader(HeaderName position)() @nogc + { + Shdr table; + + table.sh_name = position; + table.sh_type = SHT_STRTAB; + table.sh_flags = 0; + table.sh_addr = 0; + table.sh_offset = 0; + table.sh_size = 0; + table.sh_link = SHN_UNDEF; + table.sh_info = 0; + table.sh_addralign = 1; + table.sh_entsize = 0; + + this.sectionHeaders.insertBack(table); + } + + private void writeStringTables() @nogc + { + auto stringIndex = findHeader!(HeaderName.string_); + assert(stringIndex != -1); + + this.sectionHeaders[stringIndex].sh_offset = this.currentOffset; + this.sectionHeaders[stringIndex].sh_size = cast(Word) strings.length; + + output.write(cast(ubyte[]) this.strings.toStringz[0 .. this.strings.length + 1]); + this.currentOffset += this.strings.length + 1; + + auto headerStringIndex = findHeader!(HeaderName.headerString); + assert(stringIndex != -1); + + this.sectionHeaders[headerStringIndex].sh_offset = this.currentOffset; + this.sectionHeaders[headerStringIndex].sh_size = cast(Word) sections.length; + + output.write(cast(const(ubyte)[]) this.sections); + this.currentOffset += this.sections.length; + auto alignment = pad!ELFCLASS32(this.strings.length + 1 + this.sections.length); + const(ubyte)[4] padding = 0; + output.write(padding[0 .. alignment - this.strings.length - 1 - this.sections.length]); + this.currentOffset += alignment - this.strings.length - 1 - this.sections.length; + } + + private void makeSymbolHeader() @nogc + { + Shdr symbolTableHeader; + + symbolTableHeader.sh_name = HeaderName.symbol; + symbolTableHeader.sh_type = SHT_SYMTAB; + symbolTableHeader.sh_flags = 0; + symbolTableHeader.sh_addr = 0; + symbolTableHeader.sh_offset = 0; + symbolTableHeader.sh_size = 0; + // String table used by entries in this section. + symbolTableHeader.sh_link = 0; + symbolTableHeader.sh_info = 0; + symbolTableHeader.sh_addralign = 4; + symbolTableHeader.sh_entsize = Sym.sizeof; + + this.sectionHeaders.insertBack(symbolTableHeader); + } + + private void writeSymbolTable() @nogc + { + const index = findHeader!(HeaderName.symbol)(); + const stringIndex = findHeader!(HeaderName.string_)(); + const relaIndex = findHeader!(HeaderName.rela); + const textIndex = findHeader!(HeaderName.text)(); + + assert(index != -1); + assert(stringIndex != -1); + assert(relaIndex != -1); + assert(textIndex != -1); + + this.sectionHeaders[index].sh_offset = this.currentOffset; + this.sectionHeaders[index].sh_link = cast(Word) stringIndex; + this.sectionHeaders[index].sh_size = cast(Word) ((1 + symbolTable.length) * Sym.sizeof); + + this.sectionHeaders[relaIndex].sh_link = cast(Word) index; + this.sectionHeaders[relaIndex].sh_info = cast(Word) textIndex; + this.sectionHeaders[relaIndex].sh_offset = this.sectionHeaders[index].sh_offset + + this.sectionHeaders[index].sh_size; + + auto initialSymbol = initializeSymbols(); + output.write((cast(ubyte*) &initialSymbol)[0 .. Sym.sizeof]); + this.currentOffset += Sym.sizeof; + + int i = 1; + Array!Relocation symbols = Array!Relocation(this.symbolTable.byValue()); + auto rightRange = symbols[].partition!(symbol => ST_BIND(symbol.symbol.st_info) != STB_GLOBAL); + + // Greater than last local symbol. + this.sectionHeaders[index].sh_info = cast(Word) (symbols.length - rightRange.length + 1); + + foreach (ref symbol; symbols[]) + { + this.output.seek(this.sectionHeaders[relaIndex].sh_offset + this.sectionHeaders[relaIndex].sh_size, + File.Whence.set); + + if (!symbol.relocations.empty) + { + foreach (ref relocation; symbol.relocations[]) + { + relocation.r_info = R_INFO(i, R_TYPE(relocation.r_info)); + } + this.sectionHeaders[relaIndex].sh_flags = SHF_ALLOC; + const size = cast(Word) (Rela.sizeof * symbol.relocations.length); + + this.output.write((cast(ubyte*) symbol.relocations.get)[0 .. size]); + this.sectionHeaders[relaIndex].sh_size += size; + this.currentOffset += size; + } + + this.output.seek(this.sectionHeaders[index].sh_offset + i * Sym.sizeof, File.Whence.set); + output.write((cast(ubyte*) &symbol)[0 .. Sym.sizeof]); + this.currentOffset += Sym.sizeof; + ++i; + } + this.output.seek(0, File.Whence.end); + } + + void addCode(String name, ref Array!ubyte text) + @nogc + { + this.output.write(text.get); + + auto textHeaderIndex = findHeader!(HeaderName.text)(); + assert(textHeaderIndex != -1); + + this.strings.insertBack("\0"); + + Sym symbol; + // Main function + symbol.st_name = cast(Word) this.strings.length; + symbol.st_value = 0; + symbol.st_size = cast(Word) text.length; + symbol.st_info = ST_INFO(STB_GLOBAL, STT_FUNC); + symbol.st_other = 0; // char + // .text header index, half word + symbol.st_shndx = cast(Half) textHeaderIndex; + this.symbolTable[name] = Relocation(symbol); + + this.strings.insertBack(name[]); + + this.sectionHeaders[textHeaderIndex].sh_size += text.length; + this.currentOffset += text.length; + } + + void addReadOnlyData(String name, ref Array!ubyte data) @nogc + { + auto roDataIndex = findHeader!(HeaderName.roData)(); + assert(roDataIndex != -1); + + this.strings.insertBack("\0"); + + Sym symbol; + // Main function + symbol.st_name = cast(Word) this.strings.length; + symbol.st_value = 0; + symbol.st_size = cast(Word) data.length; + symbol.st_info = ST_INFO(STB_LOCAL, STT_NOTYPE); + symbol.st_other = 0; // char + // .text header index, half word + symbol.st_shndx = cast(Half) roDataIndex; + this.symbolTable[name] = Relocation(symbol); + + this.strings.insertBack(name[]); + this.readOnly.insertBack(data[]); + } + + void addExternSymbol(String name) @nogc + { + Sym usedSymbolEntry; + + this.strings.insertBack("\0"); + usedSymbolEntry.st_name = cast(Word) this.strings.length; + usedSymbolEntry.st_value = 0; + usedSymbolEntry.st_size = 0; + usedSymbolEntry.st_info = ST_INFO(STB_GLOBAL, STT_NOTYPE); + usedSymbolEntry.st_other = 0; + usedSymbolEntry.st_shndx = SHN_UNDEF; + + this.strings.insertBack(name[]); + this.strings.insertBack("\0"); + this.symbolTable[name] = Relocation(usedSymbolEntry); + } + + void relocate(String name, Rela[] usedSymbols...) @nogc + { + foreach (usedSymbol; usedSymbols) + { + Rela relocationEntry = usedSymbol; + + relocationEntry.r_info = usedSymbol.r_info; + this.symbolTable[name].relocations.insertBack(relocationEntry); + } + } + + private ptrdiff_t findHeader(HeaderName position)() + { + return countUntil!(header => header.sh_name == position)(this.sectionHeaders[]); + } + + private void makeTextHeader() @nogc + { + Shdr textHeader; + + textHeader.sh_name = HeaderName.text; + textHeader.sh_type = SHT_PROGBITS; + textHeader.sh_flags = SHF_EXECINSTR | SHF_ALLOC; + textHeader.sh_addr = 0; + textHeader.sh_offset = this.currentOffset; + textHeader.sh_size = 0; + textHeader.sh_link = SHN_UNDEF; + textHeader.sh_info = 0; + textHeader.sh_addralign = 1; + textHeader.sh_entsize = 0; + + this.sectionHeaders.insertBack(textHeader); + } + + private void initializeSectionHeaders() @nogc + { + Shdr table; + + table.sh_name = 0; + table.sh_type = SHT_NULL; + table.sh_flags = 0; + table.sh_addr = 0; + table.sh_offset = 0; + table.sh_size = 0; + table.sh_link = SHN_UNDEF; + table.sh_info = 0; + table.sh_addralign = 0; + table.sh_entsize = 0; + + this.sectionHeaders.insertBack(table); + } + + private void writeFileHeader() @nogc + { + Ehdr fileHeader; + auto headerStringIndex = findHeader!(HeaderName.headerString)(); + + assert(headerStringIndex != -1); + + // Magic number. + fileHeader.e_ident[0] = '\x7f'; + fileHeader.e_ident[1] = 'E'; + fileHeader.e_ident[2] = 'L'; + fileHeader.e_ident[3] = 'F'; + + fileHeader.e_ident[4] = ELFCLASS32; + fileHeader.e_ident[5] = ELFDATA2LSB; + fileHeader.e_ident[6] = EV_CURRENT; + fileHeader.e_ident[7] = EI_OSABI.ELFOSABI_SYSV; + fileHeader.e_ident[8] = 0; + + fileHeader.e_type = ET_REL; + fileHeader.e_machine = 0xf3; // EM_RISCV + fileHeader.e_version = EV_CURRENT; + fileHeader.e_entry = 0; + fileHeader.e_phoff = 0; + fileHeader.e_shoff = this.currentOffset; + fileHeader.e_flags = 0; + fileHeader.e_ehsize = Elf32_Ehdr.sizeof; + fileHeader.e_phentsize = 0; + fileHeader.e_phnum = 0; + fileHeader.e_shentsize = Elf32_Shdr.sizeof; + fileHeader.e_shnum = cast(Elf32_Half) this.sectionHeaders.length; + + // String table is the last one + fileHeader.e_shstrndx = cast(Half) headerStringIndex; + + output.seek(0, File.Whence.set); + output.write((cast(ubyte*) &fileHeader)[0 .. fileHeader.sizeof]); + } + + private void makeRoDataHeader() @nogc + { + Shdr table; + + table.sh_name = HeaderName.roData; + table.sh_type = SHT_PROGBITS; + table.sh_flags = SHF_ALLOC; + table.sh_addr = 0; + table.sh_offset = 0; + table.sh_size = 0; + table.sh_link = SHN_UNDEF; + table.sh_info = 0; + table.sh_addralign = 4; + table.sh_entsize = 0; + + this.sectionHeaders.insertBack(table); + } + + private void writeRoDataTable() @nogc + { + auto index = findHeader!(HeaderName.roData)(); + assert(index != -1); + + this.sectionHeaders[index].sh_offset = this.currentOffset; + this.sectionHeaders[index].sh_size = cast(Xword) this.readOnly.length; + + output.write(this.readOnly.get); + this.currentOffset += this.readOnly.length; + } + + private void makeRelaHeader() @nogc + { + Shdr table; + + table.sh_name = HeaderName.rela; + table.sh_type = SHT_RELA; + table.sh_flags = 0; + table.sh_addr = 0; + table.sh_offset = 0; + table.sh_size = 0; + table.sh_link = SHN_UNDEF; + table.sh_info = 0; + table.sh_addralign = 4; + table.sh_entsize = Rela.sizeof; + + this.sectionHeaders.insertBack(table); + } +} diff --git a/source/elna/extended.d b/source/elna/extended.d index 0473cee..ff06a0c 100644 --- a/source/elna/extended.d +++ b/source/elna/extended.d @@ -3,7 +3,333 @@ */ module elna.extended; +import core.stdc.errno; +import core.stdc.stdio; +import std.sumtype; +import std.typecons; +import tanya.os.error; +import tanya.container.array; +import tanya.container.string; + +/** + * File handle abstraction. + */ struct File { - @disable this(this); + /// Plattform dependent file type. + alias Handle = FILE*; + + /// Uninitialized file handle value. + enum Handle invalid = null; + + /** + * Relative position. + */ + enum Whence + { + /// Relative to the start of the file. + set = SEEK_SET, + /// Relative to the current cursor position. + currentt = SEEK_CUR, + /// Relative from the end of the file. + end = SEEK_END, + } + + /** + * File open modes. + */ + enum Mode + { + /// Open the file for reading. + read = 1 << 0, + /// Open the file for writing. The stream is positioned at the beginning + /// of the file. + write = 1 << 1, + /// Open the file for writing and remove its contents. + truncate = 1 << 2, + /// Open the file for writing. The stream is positioned at the end of + /// the file. + append = 1 << 3, + } + + private enum Status + { + invalid, + owned, + borrowed, + } + + private union Storage + { + Handle handle; + ErrorCode errorCode; + } + private Storage storage; + private Status status = Status.invalid; + + @disable this(scope return ref File f); + @disable this(); + + /** + * Closes the file. + */ + ~this() @nogc nothrow + { + if (this.status == Status.owned) + { + fclose(this.storage.handle); + } + this.storage.handle = invalid; + this.status = Status.invalid; + } + + /** + * Construct the object with the given system handle. The won't be claused + * in the descructor if this constructor is used. + * + * Params: + * handle = File handle to be wrapped by this structure. + */ + this(Handle handle) @nogc nothrow pure @safe + { + this.storage.handle = handle; + this.status = Status.borrowed; + } + + /** + * Returns: Plattform dependent file handle. + */ + @property Handle handle() @nogc nothrow pure @trusted + { + return valid ? this.storage.handle : invalid; + } + + /** + * Returns: An error code if an error has occurred. + */ + @property ErrorCode errorCode() @nogc nothrow pure @safe + { + return valid ? ErrorCode() : this.storage.errorCode; + } + + /** + * Returns: Whether a valid, opened file is represented. + */ + @property bool valid() @nogc nothrow pure @safe + { + return this.status != Status.invalid; + } + + /** + * Transfers the file into invalid state. + * + * Returns: The old file handle. + */ + Handle reset() @nogc nothrow pure @safe + { + if (!valid) + { + return invalid; + } + auto oldHandle = handle; + + this.status = Status.invalid; + this.storage.errorCode = ErrorCode(); + + return oldHandle; + } + + /** + * Sets stream position in the file. + * + * Params: + * offset = File offset. + * whence = File position to add the offset to. + * + * Returns: Error code if any. + */ + ErrorCode seek(size_t offset, Whence whence) @nogc nothrow + { + if (!valid) + { + return ErrorCode(ErrorCode.ErrorNo.badDescriptor); + } + if (fseek(this.storage.handle, offset, whence)) + { + return ErrorCode(cast(ErrorCode.ErrorNo) errno); + } + return ErrorCode(); + } + + /** + * Returns: Current offset or an error. + */ + SumType!(ErrorCode, size_t) tell() @nogc nothrow + { + if (!valid) + { + return typeof(return)(ErrorCode(ErrorCode.ErrorNo.badDescriptor)); + } + auto result = ftell(this.storage.handle); + + if (result < 0) + { + return typeof(return)(ErrorCode(cast(ErrorCode.ErrorNo) errno)); + } + return typeof(return)(cast(size_t) result); + } + + /** + * Params: + * buffer = Destination buffer. + * + * Returns: Bytes read. $(D_PSYMBOL ErrorCode.ErrorNo.success) means that + * while reading the file an unknown error has occurred. + */ + SumType!(ErrorCode, size_t) read(ubyte[] buffer) @nogc nothrow + { + if (!valid) + { + return typeof(return)(ErrorCode(ErrorCode.ErrorNo.badDescriptor)); + } + const bytesRead = fread(buffer.ptr, 1, buffer.length, this.storage.handle); + if (bytesRead == buffer.length || eof()) + { + return typeof(return)(bytesRead); + } + return typeof(return)(ErrorCode()); + } + + /** + * Params: + * buffer = Source buffer. + * + * Returns: Bytes written. $(D_PSYMBOL ErrorCode.ErrorNo.success) means that + * while reading the file an unknown error has occurred. + */ + SumType!(ErrorCode, size_t) write(const(ubyte)[] buffer) @nogc nothrow + { + if (!valid) + { + return typeof(return)(ErrorCode(ErrorCode.ErrorNo.badDescriptor)); + } + const bytesWritten = fwrite(buffer.ptr, 1, buffer.length, this.storage.handle); + if (bytesWritten == buffer.length) + { + return typeof(return)(buffer.length); + } + return typeof(return)(ErrorCode()); + } + + /** + * Returns: EOF status of the file. + */ + bool eof() @nogc nothrow + { + return valid && feof(this.storage.handle) != 0; + } + + /** + * Constructs a file object that will be closed in the destructor. + * + * Params: + * filename = The file to open. + * + * Returns: Opened file or an error. + */ + static File open(const(char)* filename, BitFlags!Mode mode) @nogc nothrow + { + char[3] modeBuffer = "\0\0\0"; + + if (mode.truncate) + { + modeBuffer[0] = 'w'; + if (mode.read) + { + modeBuffer[1] = '+'; + } + } + else if (mode.append) + { + modeBuffer[0] = 'a'; + if (mode.read) + { + modeBuffer[1] = '+'; + } + } + else if (mode.read) + { + modeBuffer[0] = 'r'; + if (mode.write) + { + modeBuffer[1] = '+'; + } + } + + auto newHandle = fopen(filename, modeBuffer.ptr); + auto newFile = File(newHandle); + + if (newHandle is null) + { + newFile.status = Status.invalid; + newFile.storage.errorCode = ErrorCode(cast(ErrorCode.ErrorNo) errno); + } + else + { + if (mode == BitFlags!Mode(Mode.write)) + { + rewind(newHandle); + } + newFile.status = Status.owned; + } + + return newFile; + } +} + +/** + * Reads the whole file and returns its contents. + * + * Params: + * sourceFilename = Source filename. + * + * Returns: File contents or an error. + * + * See_Also: $(D_PSYMBOL File.read) + */ +SumType!(ErrorCode, Array!ubyte) readFile(String sourceFilename) @nogc +{ + enum size_t bufferSize = 255; + auto sourceFile = File.open(sourceFilename.toStringz, BitFlags!(File.Mode)(File.Mode.read)); + + if (!sourceFile.valid) + { + return typeof(return)(sourceFile.errorCode); + } + Array!ubyte sourceText; + size_t totalRead; + size_t bytesRead; + do + { + sourceText.length = sourceText.length + bufferSize; + const readStatus = sourceFile + .read(sourceText[totalRead .. $].get) + .match!( + (ErrorCode errorCode) => nullable(errorCode), + (size_t bytesRead_) { + bytesRead = bytesRead_; + return Nullable!ErrorCode(); + } + ); + if (!readStatus.isNull) + { + return typeof(return)(readStatus.get); + } + totalRead += bytesRead; + } + while (bytesRead == bufferSize); + + sourceText.length = totalRead; + + return typeof(return)(sourceText); } diff --git a/source/elna/generator.d b/source/elna/generator.d deleted file mode 100644 index ce2b3d1..0000000 --- a/source/elna/generator.d +++ /dev/null @@ -1,660 +0,0 @@ -module elna.generator; - -import core.stdc.stdio; -import core.stdc.stdlib; -import core.stdc.string; -import elna.ir; -import tanya.container.array; -import tanya.container.string; -import tanya.memory.mmappool; -import tanya.format; - -/// Unsigned program address. -alias Elf64_Addr = void*; -/// Unsigned file offset. -alias Elf64_Off = ulong; -/// Unsigned medium integer. -alias Elf64_Half = ushort; -/// Unsigned integer. -alias Elf64_Word = uint; -/// Signed integer. -alias Elf64_Sword = int; -/// Unsigned long integer. -alias Elf64_Xword = ulong; -/// Signed long integer. -alias Elf64_Sxword = long; - -enum size_t EI_INDENT = 16; - -/** - * File header. - */ -struct Elf64_Ehdr -{ - /// ELF identification. - ubyte[EI_INDENT] e_ident; - /// Object file type. - Elf64_Half e_type; - /// Machine type. - Elf64_Half e_machine; - /// Object file version - Elf64_Word e_version; - /// Entry point address. - Elf64_Addr e_entry; - /// Program header offset. - Elf64_Off e_phoff; - /// Section header offset. - Elf64_Off e_shoff; - /// Processor-specific flags. - Elf64_Word e_flags; - /// ELF header size. - Elf64_Half e_ehsize; - /// Size of program header entry. - Elf64_Half e_phentsize; - /// Number of program header entries. - Elf64_Half e_phnum; - /// Size of section header entry. - Elf64_Half e_shentsize; - /// Number of section header entries. - Elf64_Half e_shnum; - /// Section name string table index. - Elf64_Half e_shstrndx; -} - -/** - * Section header. - */ -struct Elf64_Shdr -{ - /// Section name. - Elf64_Word sh_name; - /// Section type. - Elf64_Word sh_type; - /// Section attributes. - Elf64_Xword sh_flags; - /// Virtual address in memory. - Elf64_Addr sh_addr; - /// Offset in file. - Elf64_Off sh_offset; - /// Size of section. - Elf64_Xword sh_size; - /// Link to other section. - Elf64_Word sh_link; - /// Miscellaneous information. - Elf64_Word sh_info; - /// Address alignment boundary. - Elf64_Xword sh_addralign; - /// Size of entries, if section has table. - Elf64_Xword sh_entsize; -} - -struct Elf64_Sym -{ - /// Symbol name. - Elf64_Word st_name; - /// Type and Binding attributes. - ubyte st_info; - /// Reserved. - ubyte st_other; - /// Section table index. - Elf64_Half st_shndx; - /// Symbol value. - Elf64_Addr st_value; - /// Size of object (e.g., common). - Elf64_Xword st_size; -} - -/// Section Types, sh_type. -enum : Elf64_Word -{ - /// Marks an unused section header. - SHT_NULL = 0, - /// Contains information defined by the program. - SHT_PROGBITS = 1, - /// Contains a linker symbol table. - SHT_SYMTAB = 2, - /// Contains a string table. - SHT_STRTAB = 3, - /// Contains “Rela” type relocation entries. - SHT_RELA = 4, - /// Contains a symbol hash table - SHT_HASH = 5, - /// Contains dynamic linking tables - SHT_DYNAMIC = 6, - /// Contains note information - SHT_NOTE = 7, - /// Contains uninitialized space; does not occupy any space in the file. - SHT_NOBITS = 8, - /// Contains "Rel" type relocation entries. - SHT_REL = 9, - /// Reserved. - SHT_SHLIB = 10, - /// Contains a dynamic loader symbol table. - SHT_DYNSYM = 11, - /// Environment-specific use. - SHT_LOOS = 0x60000000, - SHT_HIOS = 0x6FFFFFFF, - /// Processor-specific use. - SHT_LOPROC = 0x70000000, - SHT_HIPROC = 0x7FFFFFFF, -} - -/** - * Section Attributes, sh_flags. - */ -enum : Elf64_Xword -{ - /// Section contains writable data. - SHF_WRITE = 0x1, - /// Section is allocated in memory image of program. - SHF_ALLOC = 0x2, - /// Section contains executable instructions. - SHF_EXECINSTR = 0x4, - /// Environment-specific use. - SHF_MASKOS = 0x0F000000, - /// Processor-specific use. - SHF_MASKPROC = 0xF0000000, -} - -enum : Elf64_Word -{ - /// Not visible outside the object file. - STB_LOCAL = 0, - /// Global symbol, visible to all object files. - STB_GLOBAL = 1, - /// Global scope, but with lower precedence than global symbols. - STB_WEAK = 2, - /// Environment-specific use. - STB_LOOS = 10, - STB_HIOS = 12, - /// Processor-specific use. - STB_LOPROC = 13, - STB_HIPROC = 15, -} - -enum : Elf64_Word -{ - /// No type specified (e.g., an absolute symbol). - STT_NOTYPE = 0, - /// Data object. - STT_OBJECT = 1, - /// Function entry point. - STT_FUNC = 2, - /// Symbol is associated with a section. - STT_SECTION = 3, - /// Source file associated with the object file. - STT_FILE = 4, - /// Environment-specific use. - STT_LOOS = 10, - STT_HIOS = 12, - /// Processor-specific use. - STT_LOPROC = 13, - STT_HIPROC = 15, -} - -Elf64_Ehdr makeFileHeader(Elf64_Off sectionHeaderOffset, - Elf64_Half sectionHeaderCount, - Elf64_Half stringIndex) @nogc -{ - Elf64_Ehdr header; - - // Magic number. - header.e_ident[0] = '\x7f'; - header.e_ident[1] = 'E'; - header.e_ident[2] = 'L'; - header.e_ident[3] = 'F'; - - // File class. - header.e_ident[4] = EI_CLASS.ELFCLASS64; - - // Data encoding. - header.e_ident[5] = EI_DATA.ELFDATA2LSB; - - // Version. - header.e_ident[6] = EV_CURRENT; - - // OS/ABI identification. - header.e_ident[7] = EI_OSABI.ELFOSABI_SYSV; - - // ABI version. - header.e_ident[8] = 0; - - // Size of e_ident[]. - header.e_ident[15] = 0; - - header.e_type = ET_REL; - header.e_machine = 0x3e; // EM_X86_64: AMD x86-64 architecture - header.e_version = EV_CURRENT; - header.e_entry = null; - header.e_phoff = 0; - header.e_shoff = sectionHeaderOffset; - header.e_flags = 0; - header.e_ehsize = Elf64_Ehdr.sizeof; - header.e_phentsize = 0; - header.e_phnum = 0; - header.e_shentsize = Elf64_Shdr.sizeof; - header.e_shnum = sectionHeaderCount; - header.e_shstrndx = stringIndex; - - return header; -} - -enum char[33] sectionStringTable = "\0.symtab\0.strtab\0.shstrtab\0.text\0"; - -Elf64_Shdr makeTextHeader(Elf64_Off offset, Elf64_Xword size) @nogc -{ - Elf64_Shdr table; - - table.sh_name = 0x1b; - table.sh_type = SHT_PROGBITS; - table.sh_flags = SHF_EXECINSTR | SHF_ALLOC; - table.sh_addr = null; - table.sh_offset = offset; - table.sh_size = size; - table.sh_link = SHN_UNDEF; - table.sh_info = 0; - table.sh_addralign = 1; - table.sh_entsize = 0; - - return table; -} - -Elf64_Shdr makeDataHeader(Elf64_Off offset, Elf64_Xword size) @nogc -{ - Elf64_Shdr table; - - table.sh_name = 0x21; - table.sh_type = SHT_PROGBITS; - table.sh_flags = SHF_WRITE | SHF_ALLOC; - table.sh_addr = null; - table.sh_offset = offset; - table.sh_size = size; - table.sh_link = SHN_UNDEF; - table.sh_info = 0; - table.sh_addralign = 1; - table.sh_entsize = 0; - - return table; -} - -Elf64_Shdr makeSymtableHeader(Elf64_Off offset, Elf64_Xword size, Elf64_Word entriesCount) @nogc -{ - Elf64_Shdr table; - - table.sh_name = 0x01; - table.sh_type = SHT_SYMTAB; - table.sh_flags = 0; - table.sh_addr = null; - table.sh_offset = offset; - table.sh_size = size; - table.sh_link = 0x03; // String table used by entries in this section. - table.sh_info = entriesCount; - table.sh_addralign = 8; - table.sh_entsize = Elf64_Sym.sizeof; - - return table; -} - -Elf64_Shdr makeStringHeader(Elf64_Word stringIndex, Elf64_Off offset, Elf64_Xword size) @nogc -{ - Elf64_Shdr table; - - table.sh_name = stringIndex; - table.sh_type = SHT_STRTAB; - table.sh_flags = 0; - table.sh_addr = null; - table.sh_offset = offset; - table.sh_size = size; - table.sh_link = SHN_UNDEF; - table.sh_info = 0; - table.sh_addralign = 1; - table.sh_entsize = 0; - - return table; -} - -Elf64_Shdr makeInitialHeader() @nogc -{ - Elf64_Shdr table; - - table.sh_name = 0; - table.sh_type = SHT_NULL; - table.sh_flags = 0; - table.sh_addr = null; - table.sh_offset = 0; - table.sh_size = 0; - table.sh_link = SHN_UNDEF; - table.sh_info = 0; - table.sh_addralign = 0; - table.sh_entsize = 0; - - return table; -} - -Elf64_Sym makeInitialSymTable() @nogc -{ - Elf64_Sym table; - - table.st_name = 0; - table.st_info = 0; - table.st_other = 0; - table.st_shndx = 0; - table.st_value = null; - table.st_size = 0; - - return table; -} - -Elf64_Sym makeMainSymTable(Elf64_Half textIndex) @nogc -{ - Elf64_Sym table; - - table.st_name = 0x01; - table.st_info = ELF32_ST_INFO(STB_GLOBAL, STT_FUNC); - table.st_other = 0; - table.st_shndx = textIndex; - table.st_value = null; - table.st_size = 0; - - return table; -} - -ubyte ELF32_ST_BIND(ubyte i) @nogc nothrow pure @safe -{ - return i >> 4; -} - -ubyte ELF32_ST_TYPE(ubyte i) @nogc nothrow pure @safe -{ - return i & 0xf; -} - -ubyte ELF32_ST_INFO(ubyte b, ubyte t) @nogc nothrow pure @safe -{ - return cast(ubyte) ((b << 4) + (t & 0xf)); -} - -/// Special Section Indices. -enum : Elf64_Half -{ - /// Used to mark an undefined or meaningless section reference. - SHN_UNDEF = 0, - /// Processor-specific use. - SHN_LOPROC = 0xFF00, - SHN_HIPROC = 0xFF1F, - /// Environment-specific use. - SHN_LOOS = 0xFF20, - SHN_HIOS = 0xFF3F, - /// Indicates that the corresponding reference is an absolute value. - SHN_ABS = 0xFFF1, - /** - * Indicates a symbol that has been declared as a common block (Fortran - * COMMON or C tentative declaration). - */ - SHN_COMMON = 0xFFF2, -} - -/** - * Object File Classes, e_ident[EI_CLASS]. - */ -enum EI_CLASS : ubyte -{ - /// 32-bit objects. - ELFCLASS32 = 1, - /// 64-bit objects. - ELFCLASS64 = 2, -} - -enum ubyte EV_CURRENT = 1; - -/** - * Data Encodings, e_ident[EI_DATA]. - */ -enum EI_DATA : ubyte -{ - /// Object file data structures are little-endian. - ELFDATA2LSB = 1, - /// Object file data structures are big-endian. - ELFDATA2MSB = 2, -} - -/** - * Operating System and ABI Identifiers, e_ident[EI_OSABI]. - */ -enum EI_OSABI : ubyte -{ - /// System V ABI. - ELFOSABI_SYSV = 0, - /// HP-UX operating system. - ELFOSABI_HPUX = 1, - /// Standalone (embedded) application. - ELFOSABI_STANDALONE = 255, -} - -enum : Elf64_Half -{ - ET_NONE = 0, /// No file type. - ET_REL = 1, /// Relocatable object file. - ET_EXEC = 2, /// Executable file. - ET_DYN = 3, /// Shared object file. - ET_CORE = 4, /// Core file. - ET_LOOS = 0xFE00, /// Environment-specific use. - ET_HIOS = 0xFEFF, - ET_LOPROC = 0xFF00, /// Processor-specific use. - ET_HIPROC = 0xFFFF, -} - -private size_t pad(size_t value) @nogc -{ - return (value / 8 + 1) * 8; -} - -struct Symbol -{ - String name; - Array!ubyte instructions; -} - -/* -.text - .globl main - .type main, @function -main: - movl $3, %eax - ret -*/ -immutable ubyte[] instructions = [ - // Opcode of pushq is “0x50 + r”, where “r” is the register opcode. - // Register opcode of %rbq is 5. - 0x50 + 5, // push% %rbp - 0x48, 0x89, 0xe5, // movq %rsp, %rbp - - 0xb8, 0x03, 0x00, 0x00, 0x00, // movl $3, %eax - - // Epilogue. - 0x48, 0x89, 0xec, // movq %rbp, %rsp - 0x58 + 5, // popq %rbp - 0xc3, // ret -]; - -void writeObject(Definition ast, String outputFilename) @nogc -{ - auto handle = fopen(outputFilename.toStringz, "wb"); - - if (handle is null) - { - perror("writing sample"); - return; - } - scope (exit) - { - fclose(handle); - } - - size_t currentOffset = Elf64_Ehdr.sizeof; - Array!Elf64_Shdr sectionHeaders; - Array!Elf64_Sym symbolEntries; - - // Prologue - Array!ubyte asmTemplate = Array!ubyte(cast(ubyte[]) [ - // Opcode of pushq is “0x50 + r”, where “r” is the register opcode. - // Register opcode of %rbq is 5. - 0x50 + 5, // pushq %rbp - 0x48, 0x89, 0xe5, // movq %rsp, %rbp - ]); - int i = 1; - foreach (statement; ast.statements[]) - { - if ((cast(Number) statement.subroutine.lhs) !is null) - { - // Opcode of mov is “0xb8 + r”, where “r” is the register opcode. - // Register opcode of %eax is 0. - asmTemplate.insertBack(cast(ubyte) 0xb8); // movl $x, %eax; where $x is a number. - asmTemplate.insertBack((cast(ubyte*) &(cast(Number) statement.subroutine.lhs).value)[0 .. int.sizeof]); - } - else if ((cast(Variable) statement.subroutine.lhs) !is null) - { - // movl -x(%rbp), %ebx; where x is a number. - asmTemplate.insertBack(cast(ubyte[]) [0x8b, 0x45]); - const disposition = (cast(Variable) statement.subroutine.lhs).counter * (-4); - asmTemplate.insertBack((cast(ubyte*) &disposition)[0 .. 1]); - } - if ((cast(Number) statement.subroutine.rhs) !is null) - { - // Opcode of mov is “0xb8 + r”, where “r” is the register opcode. - // Register opcode of %ebx is 3. - asmTemplate.insertBack(cast(ubyte) 0xbb); // movl $x, %ebx; where $x is a number. - asmTemplate.insertBack((cast(ubyte*) &(cast(Number) statement.subroutine.rhs).value)[0 .. int.sizeof]); - } - else if ((cast(Variable) statement.subroutine.rhs) !is null) - { - // movl -x(%rbp), %ebx; where x is a number. - asmTemplate.insertBack(cast(ubyte[]) [0x8b, 0x5d]); - const disposition = (cast(Variable) statement.subroutine.rhs).counter * (-4); - asmTemplate.insertBack((cast(ubyte*) &disposition)[0 .. 1]); - } - // Calculate the result and assign it to a variable on the stack. - asmTemplate.insertBack(cast(ubyte[]) [0x01, 0xd8]); // add %ebx, %eax - - asmTemplate.insertBack(cast(ubyte[]) [0x89, 0x45]); // movl %eax, -x(%rbp); where x is a number. - const disposition = i * (-4); - asmTemplate.insertBack((cast(ubyte*) &disposition)[0 .. 1]); - ++i; - } - // Epilogue. - asmTemplate.insertBack(cast(ubyte[]) [ - 0x48, 0x89, 0xec, // movq %rbp, %rsp - 0x58 + 5, // popq %rbp - 0xc3, // ret - ]); - - Symbol[1] symbols = [Symbol(String("main"), asmTemplate)]; - - sectionHeaders.insertBack(makeInitialHeader()); - symbolEntries.insertBack(makeInitialSymTable()); - - String stringTable = String("\0"); - foreach (symbol; symbols[]) - { - stringTable.insertBack(symbol.name[]); - stringTable.insertBack('\0'); - - sectionHeaders.insertBack(makeTextHeader(currentOffset, symbol.instructions.length)); - currentOffset = pad(currentOffset + symbol.instructions.length); - - symbolEntries.insertBack(makeMainSymTable(cast(Elf64_Half) (sectionHeaders.length - 1))); - } - - const symbolTableSize = (symbols.length + 1) * Elf64_Sym.sizeof; - sectionHeaders.insertBack(makeSymtableHeader(currentOffset, symbolTableSize, symbols.length)); - currentOffset += symbolTableSize; - - sectionHeaders.insertBack(makeStringHeader(0x09, currentOffset, stringTable.length)); - currentOffset += stringTable.length; - - sectionHeaders.insertBack(makeStringHeader(0x11, currentOffset, sectionStringTable.length)); - currentOffset = pad(currentOffset + sectionStringTable.length); - - auto fileHeader = makeFileHeader(currentOffset, 5, 4); - - version (none) - { - printf("%.2x\n", cast(uint) currentOffset); - } - ubyte[8] padding = 0; - size_t codeLength = stringTable.length + sectionStringTable.length; - - fwrite(&fileHeader, 8, Elf64_Ehdr.sizeof / 8, handle); - foreach (symbol; symbols[]) - { - immutable size_t instructionsLength = pad(symbol.instructions.length); - fwrite(symbol.instructions.get.ptr, 1, symbol.instructions.length, handle); - fwrite(padding.ptr, 1, instructionsLength - symbol.instructions.length, handle); - codeLength += instructionsLength; - } - fwrite(symbolEntries.get.ptr, Elf64_Sym.sizeof, symbolEntries.length, handle); - fwrite(stringTable.get.ptr, 1, stringTable.length, handle); - fwrite(sectionStringTable.ptr, 1, sectionStringTable.length, handle); - fwrite(padding.ptr, pad(codeLength) - codeLength, 1, handle); - fwrite(sectionHeaders.get.ptr, Elf64_Shdr.sizeof, sectionHeaders.length, handle); -} - -String generate(Definition ast) @nogc -{ - // Prologue - String asmTemplate = ".text - .globl main - .type main, @function -main: - pushq %rbp - movq %rsp, %rbp -"; - - /* Allocate space on the stack for local variables. - asmTemplate.insertBack(" sub $"); - asmTemplate.insertBack(format!"{}"(ast.statements.length)[]); - asmTemplate.insertBack(", $esp\n"); */ - - int i = 1; - foreach (statement; ast.statements[]) - { - if ((cast(Number) statement.subroutine.lhs) !is null) - { - asmTemplate.insertBack(" movl $"); - asmTemplate.insertBack(format!"{}"((cast(Number) statement.subroutine.lhs).value)[]); - asmTemplate.insertBack(", %eax\n"); - } - else if ((cast(Variable) statement.subroutine.lhs) !is null) - { - asmTemplate.insertBack(" movl -"); - asmTemplate.insertBack(format!"{}"((cast(Variable) statement.subroutine.lhs).counter * 4)[]); - asmTemplate.insertBack("(%rbp), %eax\n"); - } - if ((cast(Number) statement.subroutine.rhs) !is null) - { - asmTemplate.insertBack(" movl $"); - asmTemplate.insertBack(format!"{}"((cast(Number) statement.subroutine.rhs).value)[]); - asmTemplate.insertBack(", %ebx\n"); - } - else if ((cast(Variable) statement.subroutine.rhs) !is null) - { - asmTemplate.insertBack(" movl -"); - asmTemplate.insertBack(format!"{}"((cast(Variable) statement.subroutine.rhs).counter * 4)[]); - asmTemplate.insertBack("(%rbp), %ebx\n"); - } - // Calculate the result and assign it to a variable on the stack. - asmTemplate.insertBack(" add %ebx, %eax\n"); - asmTemplate.insertBack(" movl %eax, -"); - asmTemplate.insertBack(format!"{}"(i * 4)[]); - asmTemplate.insertBack("(%rbp)\n"); - ++i; - } - - // Epilogue. - asmTemplate.insertBack(" movq %rbp, %rsp - popq %rbp - ret -"); - - return asmTemplate; -} diff --git a/source/elna/ir.d b/source/elna/ir.d index e2a8df4..4ff2288 100644 --- a/source/elna/ir.d +++ b/source/elna/ir.d @@ -1,144 +1,220 @@ module elna.ir; +import core.stdc.stdlib; import parser = elna.parser; import tanya.container.array; import tanya.container.hashtable; import tanya.container.string; import tanya.memory.allocator; -import tanya.memory.mmappool; +public import elna.parser : BinaryOperator; + +/** + * Mapping between the parser and IR AST. + */ +struct ASTMapping +{ + alias Node = .Node; + alias Definition = .Definition; + alias Statement = .Operand; + alias BangStatement = .Operand; + alias Block = .Definition; + alias Expression = .Operand; + alias Number = .Number; + alias Variable = .Number; + alias BinaryExpression = .Variable; + +} + +/** + * IR visitor. +*/ +extern(C++, "elna", "ir") +abstract class IRVisitor +{ + abstract void visit(Node) @nogc; + abstract void visit(Definition) @nogc; + abstract void visit(Operand) @nogc; + abstract void visit(BinaryExpression) @nogc; + abstract void visit(Variable) @nogc; + abstract void visit(Number) @nogc; +} + +/** + * AST node. + */ +extern(C++, "elna", "ir") +abstract class Node +{ + abstract void accept(IRVisitor) @nogc; +} /** * Definition. */ -class Definition +extern(C++, "elna", "ir") +class Definition : Node { - char[] identifier; - Array!Statement statements; - Array!VariableDeclaration variableDeclarations; + BinaryExpression* statements; + size_t statementsLength; + Operand result; + + override void accept(IRVisitor visitor) @nogc; } -class Statement -{ - Subroutine subroutine; -} - -abstract class Expression +extern(C++, "elna", "ir") +abstract class Statement : Node { } -class Number : Expression +extern(C++, "elna", "ir") +abstract class Operand : Node +{ + override void accept(IRVisitor visitor) @nogc; +} + +extern(C++, "elna", "ir") +class Number : Operand { int value; + + override void accept(IRVisitor visitor) @nogc; } -class Variable : Expression +extern(C++, "elna", "ir") +class Variable : Operand { size_t counter; + + override void accept(IRVisitor visitor) @nogc; } -class VariableDeclaration +extern(C++, "elna", "ir") +class BinaryExpression : Statement { - String identifier; + Operand lhs, rhs; + BinaryOperator operator; + + this(Operand lhs, Operand rhs, BinaryOperator operator) @nogc; + + override void accept(IRVisitor visitor) @nogc; } -class Subroutine +extern(C++, "elna", "ir") +class BangExpression : Statement { - Expression lhs, rhs; + Operand operand; + + this(Operand operand); + + override void accept(IRVisitor visitor) @nogc; } -private Number transformNumber(parser.Number number) @nogc +final class TransformVisitor : parser.ParserVisitor!ASTMapping { - return MmapPool.instance.make!Number(number.value); -} + private HashTable!(String, int) constants; + private BinaryExpression* statements; + private size_t statementsLength; -private Variable transformSubroutine(parser.Subroutine subroutine, - ref Array!Statement statements, - ref HashTable!(String, int) constants) @nogc -{ - auto target = MmapPool.instance.make!Subroutine; - target.lhs = transformExpression(subroutine.lhs, statements, constants); - target.rhs = transformExpression(subroutine.rhs, statements, constants); - - auto newStatement = MmapPool.instance.make!Statement; - newStatement.subroutine = target; - statements.insertBack(newStatement); - - auto newVariable = MmapPool.instance.make!Variable; - newVariable.counter = statements.length; - - return newVariable; -} - -private Expression transformExpression(parser.Expression expression, - ref Array!Statement statements, - ref HashTable!(String, int) constants) @nogc -{ - if ((cast(parser.Number) expression) !is null) + ASTMapping.Node visit(parser.Node node) @nogc { - auto numberExpression = MmapPool.instance.make!Number; - numberExpression.value = (cast(parser.Number) expression).value; + assert(false, "Not implemented"); + } + + ASTMapping.Definition visit(parser.Definition definition) @nogc + { + assert(false, "Not implemented"); + } + + ASTMapping.BangStatement visit(parser.BangStatement statement) @nogc + { + return statement.expression.accept(this); + } + + ASTMapping.Block visit(parser.Block block) @nogc + { + auto target = defaultAllocator.make!Definition; + this.constants = transformConstants(block.definitions); + + target.result = block.statement.accept(this); + target.statements = this.statements; + target.statementsLength = this.statementsLength; + + return target; + } + + ASTMapping.Expression visit(parser.Expression expression) @nogc + { + 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 + { + auto numberExpression = defaultAllocator.make!Number; + numberExpression.value = number.value; return numberExpression; } - if ((cast(parser.Variable) expression) !is null) + + ASTMapping.Variable visit(parser.Variable variable) @nogc { - auto numberExpression = MmapPool.instance.make!Number; - numberExpression.value = constants[(cast(parser.Variable) expression).identifier]; + auto numberExpression = defaultAllocator.make!Number; + numberExpression.value = this.constants[variable.identifier]; return numberExpression; } - else if ((cast(parser.Subroutine) expression) !is null) - { - return transformSubroutine(cast(parser.Subroutine) expression, statements, constants); - } - return null; -} -Expression transformStatement(parser.Statement statement, - ref Array!Statement statements, - ref HashTable!(String, int) constants) @nogc -{ - if ((cast(parser.BangStatement) statement) !is null) + ASTMapping.BinaryExpression visit(parser.BinaryExpression binaryExpression) @nogc { - return transformExpression((cast(parser.BangStatement) statement).expression, statements, constants); - } - return null; -} + auto target = defaultAllocator.make!BinaryExpression( + binaryExpression.lhs.accept(this), + binaryExpression.rhs.accept(this), + binaryExpression.operator + ); + this.statements = cast(BinaryExpression*) + realloc(this.statements, (this.statementsLength + 1) * BinaryExpression.sizeof); + this.statements[this.statementsLength++] = target; -HashTable!(String, int) transformConstants(ref Array!(parser.Definition) definitions) @nogc -{ - typeof(return) constants; + auto newVariable = defaultAllocator.make!Variable; + newVariable.counter = this.statementsLength; - foreach (definition; definitions[]) - { - constants[definition.identifier] = definition.number.value; + return newVariable; } - return constants; -} - -Array!VariableDeclaration transformVariableDeclarations(ref Array!(parser.VariableDeclaration) variableDeclarations) -@nogc -{ - typeof(return) variables; - - foreach (ref variableDeclaration; variableDeclarations) + private Number transformNumber(parser.Number number) @nogc { - auto newDeclaration = MmapPool.instance.make!VariableDeclaration; - newDeclaration.identifier = variableDeclaration.identifier; - variables.insertBack(newDeclaration); + return defaultAllocator.make!Number(number.value); } - return variables; -} - -Definition transform(parser.Block block) @nogc -{ - auto target = MmapPool.instance.make!Definition; - auto constants = transformConstants(block.definitions); - - transformStatement(block.statement, target.statements, constants); - target.variableDeclarations = transformVariableDeclarations(block.variableDeclarations); - - return target; + override Operand visit(parser.Statement statement) @nogc + { + if ((cast(parser.BangStatement) statement) !is null) + { + return (cast(parser.BangStatement) statement).accept(this); + } + assert(false, "Invalid statement type"); + } + + private HashTable!(String, int) transformConstants(ref Array!(parser.Definition) definitions) @nogc + { + typeof(return) constants; + + foreach (definition; definitions[]) + { + constants[definition.identifier] = definition.number.value; + } + + return constants; + } } diff --git a/source/elna/lexer.d b/source/elna/lexer.d index c47aae0..45a641c 100644 --- a/source/elna/lexer.d +++ b/source/elna/lexer.d @@ -7,30 +7,30 @@ import elna.result; import std.range; import tanya.container.array; import tanya.container.string; -import tanya.memory.mmappool; +extern(C++, "elna") struct Token { - enum Type + enum Type : ushort { - number, - subroutine, // Operator. - let, - identifier, - equals, - var, - semicolon, - leftParen, - rightParen, - bang, - dot, - comma, + number = 0, + operator = 1, + let = 2, + identifier = 3, + equals = 4, + var = 5, + semicolon = 6, + leftParen = 7, + rightParen = 8, + bang = 9, + dot = 10, + comma = 11, } union Value { int number; - String identifier; + const(char)* identifier; } private Type type; @@ -39,62 +39,21 @@ struct Token @disable this(); - this(Type type, Position position) @nogc nothrow pure @safe - { - this.type = type; - this.position_ = position; - } - - this(Type type, int value, Position position) @nogc nothrow pure @trusted - in (type == Type.number) - { - this(type, position); - this.value_.number = value; - } - - this()(Type type, auto ref String value, Position position) - @nogc nothrow pure @trusted - in (type == Type.identifier) - { - this(type, position); - this.value_.identifier = value; - } + this(Type type, Position position) @nogc nothrow pure @safe; + this(Type type, int value, Position position) @nogc nothrow pure @trusted; + this(Type type, const(char)* value, Position position) @nogc nothrow; /** - * Params: - * type = Expected type. - * - * Returns: Whether this token is of the expected type. + * Returns: Expected token type. */ - bool ofType(Type type) const @nogc nothrow pure @safe - { - return this.type == type; - } - - @property auto value(Type type)() @nogc nothrow pure @trusted - in (ofType(type)) - { - static if (type == Type.number) - { - return this.value_.number; - } - else static if (type == Type.identifier) - { - return this.value_.identifier; - } - else - { - static assert(false, "This type doesn't have a value"); - } - } + Type of() const @nogc nothrow pure @safe; + const(char)* identifier() const @nogc nothrow pure; + int number() const @nogc nothrow pure; /** * Returns: The token position in the source text. */ - @property const(Position) position() const @nogc nothrow pure @safe - { - return this.position_; - } + @property const(Position) position() const @nogc nothrow pure @safe; } /** @@ -102,10 +61,10 @@ struct Token */ struct Source { - char[] buffer; + const(char)* buffer; Position position; - this(char[] buffer) @nogc nothrow pure @safe + this(const(char)* buffer) @nogc nothrow pure @safe { this.buffer = buffer; } @@ -114,7 +73,7 @@ struct Source bool empty() @nogc nothrow pure @safe { - return this.length == 0; + return this.buffer is null || this.buffer[0] == '\0'; } char front() @nogc nothrow pure @safe @@ -123,130 +82,36 @@ struct Source return this.buffer[0]; } - void popFront() @nogc nothrow pure @safe + void popFront() @nogc nothrow pure in (!empty) { - this.buffer = buffer[1 .. $]; + ++this.buffer; ++this.position.column; } - void breakLine() @nogc nothrow pure @safe + void breakLine() @nogc nothrow pure in (!empty) { - this.buffer = buffer[1 .. $]; + ++this.buffer; ++this.position.line; this.position.column = 1; } - @property size_t length() const @nogc nothrow pure @safe + @property size_t length() const @nogc nothrow pure { - return this.buffer.length; + return strlen(this.buffer); } - char opIndex(size_t index) @nogc nothrow pure @safe - in (index < length) + char opIndex(size_t index) @nogc nothrow pure { return this.buffer[index]; } - char[] opSlice(size_t i, size_t j) @nogc nothrow pure @safe - in - { - assert(i <= j); - assert(j <= length); - } - do + const(char)[] opSlice(size_t i, size_t j) @nogc nothrow pure { return this.buffer[i .. j]; } } -Array!Token lex(char[] buffer) @nogc -{ - Array!Token tokens; - auto source = Source(buffer); - - while (!source.empty) - { - if (source.front == ' ') - { - source.popFront; - } - else if (source.front >= '0' && source.front <= '9') // Multi-digit. - { - tokens.insertBack(Token(Token.Type.number, source.front - '0', source.position)); - source.popFront; - } - else if (source.front == '=') - { - tokens.insertBack(Token(Token.Type.equals, source.position)); - source.popFront; - } - else if (source.front == '(') - { - tokens.insertBack(Token(Token.Type.leftParen, source.position)); - source.popFront; - } - else if (source.front == ')') - { - tokens.insertBack(Token(Token.Type.rightParen, source.position)); - source.popFront; - } - else if (source.front == ';') - { - tokens.insertBack(Token(Token.Type.semicolon, source.position)); - source.popFront; - } - else if (source.front == ',') - { - tokens.insertBack(Token(Token.Type.comma, source.position)); - source.popFront; - } - else if (source.front == '!') - { - tokens.insertBack(Token(Token.Type.bang, source.position)); - source.popFront; - } - else if (source.front == '.') - { - tokens.insertBack(Token(Token.Type.dot, source.position)); - source.popFront; - } - else if (isalpha(source.front)) - { - size_t i = 1; - while (i < source.length && isalpha(source[i])) - { - ++i; - } - if (source[0 .. i] == "const") - { - tokens.insertBack(Token(Token.Type.let, source.position)); - } - else if (source[0 .. i] == "var") - { - tokens.insertBack(Token(Token.Type.var, source.position)); - } - else - { - auto identifier = String(source[0 .. i]); - tokens.insertBack(Token(Token.Type.identifier, identifier, source.position)); - } - source.popFrontN(i); - } - else if (source.front == '+') // Multi-character, random special characters. - { - tokens.insertBack(Token(Token.Type.subroutine, source.position)); - source.popFront; - } - else if (source.front == '\n') - { - source.breakLine; - } - else - { - return typeof(tokens)(); // Error. - } - } - return tokens; -} +extern(C++, "elna") +Token* lex(const(char)* buffer, CompileError* compileError, size_t* length) @nogc; diff --git a/source/elna/parser.d b/source/elna/parser.d index 28a090b..18e31bd 100644 --- a/source/elna/parser.d +++ b/source/elna/parser.d @@ -1,106 +1,165 @@ module elna.parser; +import core.stdc.string; import elna.lexer; import elna.result; import tanya.container.array; import tanya.container.string; import tanya.memory.allocator; -import tanya.memory.mmappool; +import std.array; + +/** + * Parser visitor. + */ +interface ParserVisitor(Mapping) +{ + Mapping.Node visit(Node) @nogc; + Mapping.Definition visit(Definition) @nogc; + Mapping.Statement visit(Statement) @nogc; + Mapping.BangStatement visit(BangStatement) @nogc; + Mapping.Block visit(Block) @nogc; + Mapping.Expression visit(Expression) @nogc; + Mapping.Number visit(Number) @nogc; + Mapping.Variable visit(Variable) @nogc; + Mapping.BinaryExpression visit(BinaryExpression) @nogc; +} + +/** + * AST node. + */ +abstract class Node +{ + Mapping.Node accept(Mapping)(ParserVisitor!Mapping visitor) @nogc + { + return visitor.visit(this); + } +} /** * Constant definition. */ -class Definition +class Definition : Node { Number number; String identifier; + + Mapping.Definition accept(Mapping)(ParserVisitor!Mapping visitor) @nogc + { + return visitor.visit(this); + } } -/** - * Variable declaration. - */ -class VariableDeclaration -{ - String identifier; -} - -abstract class Statement +abstract class Statement : Node { + Mapping.Statement accept(Mapping)(ParserVisitor!Mapping visitor) @nogc + { + return visitor.visit(this); + } } class BangStatement : Statement { Expression expression; + + Mapping.BangStatement accept(Mapping)(ParserVisitor!Mapping visitor) @nogc + { + return visitor.visit(this); + } } -class Block +class Block : Node { Array!Definition definitions; - Array!VariableDeclaration variableDeclarations; Statement statement; + + Mapping.Block accept(Mapping)(ParserVisitor!Mapping visitor) @nogc + { + return visitor.visit(this); + } } -abstract class Expression +abstract class Expression : Node { + Mapping.Expression accept(Mapping)(ParserVisitor!Mapping visitor) @nogc + { + return visitor.visit(this); + } } class Number : Expression { int value; + + Mapping.Number accept(Mapping)(ParserVisitor!Mapping visitor) @nogc + { + return visitor.visit(this); + } } class Variable : Expression { String identifier; + + Mapping.Variable accept(Mapping)(ParserVisitor!Mapping visitor) @nogc + { + return visitor.visit(this); + } } -class Subroutine : Expression +extern(C++, "elna") +enum BinaryOperator +{ + sum, + subtraction +} + +class BinaryExpression : Expression { Expression lhs, rhs; + BinaryOperator operator; + + this(Expression lhs, Expression rhs, String operator) @nogc + { + this.lhs = lhs; + this.rhs = rhs; + if (operator == "+") + { + this.operator = BinaryOperator.sum; + } + else if (operator == "-") + { + this.operator = BinaryOperator.subtraction; + } + else + { + assert(false, "Invalid binary operator"); + } + } + + Mapping.BinaryExpression accept(Mapping)(ParserVisitor!Mapping visitor) @nogc + { + return visitor.visit(this); + } } -private Result!Expression parseExpression(ref Array!(Token).Range tokens) @nogc -in (!tokens.empty, "Expected expression, got end of stream") +private Result!Expression parseFactor(ref Token[] tokens) @nogc +in (!tokens.empty, "Expected factor, got end of stream") { - if (tokens.front.ofType(Token.Type.number)) + if (tokens.front.of() == Token.Type.identifier) { - auto number = MmapPool.instance.make!Number; - number.value = tokens.front.value!(Token.Type.number); - tokens.popFront; - return Result!Expression(number); - } - else if (tokens.front.ofType(Token.Type.identifier)) - { - auto variable = MmapPool.instance.make!Variable; - variable.identifier = tokens.front.value!(Token.Type.identifier); + auto variable = defaultAllocator.make!Variable; + variable.identifier = tokens.front.identifier()[0 .. strlen(tokens.front.identifier())]; tokens.popFront; return Result!Expression(variable); } - else if (tokens.front.ofType(Token.Type.subroutine)) + else if (tokens.front.of() == Token.Type.number) { - auto subroutine = MmapPool.instance.make!Subroutine; + auto number = defaultAllocator.make!Number; + number.value = tokens.front.number(); tokens.popFront; - auto expression = parseExpression(tokens); - if (expression.valid) - { - subroutine.lhs = expression.result; - } - else - { - return Result!Expression("Expected left-hand side to be an expression", tokens.front.position); - } - expression = parseExpression(tokens); - if (expression.valid) - { - subroutine.rhs = expression.result; - } - else - { - return Result!Expression("Expected left-hand side to be an expression", tokens.front.position); - } - return Result!Expression(subroutine); + return Result!Expression(number); } - else if (tokens.front.ofType(Token.Type.leftParen)) + else if (tokens.front.of() == Token.Type.leftParen) { tokens.popFront; @@ -109,22 +168,53 @@ in (!tokens.empty, "Expected expression, got end of stream") tokens.popFront; return expression; } - return Result!Expression("Expected an expression", tokens.front.position); + return Result!Expression("Expected a factor", tokens.front.position); } -private Result!Definition parseDefinition(ref Array!Token.Range tokens) @nogc +private Result!Expression parseTerm(ref Token[] tokens) @nogc +{ + return parseFactor(tokens); +} + +private Result!Expression parseExpression(ref Token[] tokens) @nogc +in (!tokens.empty, "Expected expression, got end of stream") +{ + auto term = parseTerm(tokens); + if (!term.valid || tokens.empty || tokens.front.of() != Token.Type.operator) + { + return term; + } + auto operator = String(tokens.front.identifier()[0 .. strlen(tokens.front.identifier())]); + tokens.popFront; + + auto expression = parseExpression(tokens); + + if (expression.valid) + { + auto binaryExpression = defaultAllocator + .make!BinaryExpression(term.result, expression.result, operator); + + return Result!Expression(binaryExpression); + } + else + { + return Result!Expression("Expected right-hand side to be an expression", tokens.front.position); + } +} + +private Result!Definition parseDefinition(ref Token[] tokens) @nogc in (!tokens.empty, "Expected definition, got end of stream") { - auto definition = MmapPool.instance.make!Definition; - definition.identifier = tokens.front.value!(Token.Type.identifier); // Copy. + auto definition = defaultAllocator.make!Definition; + definition.identifier = tokens.front.identifier()[0 .. strlen(tokens.front.identifier())]; // Copy. tokens.popFront(); tokens.popFront(); // Skip the equals sign. - if (tokens.front.ofType(Token.Type.number)) + if (tokens.front.of() == Token.Type.number) { - auto number = MmapPool.instance.make!Number; - number.value = tokens.front.value!(Token.Type.number); + auto number = defaultAllocator.make!Number; + number.value = tokens.front.number(); definition.number = number; tokens.popFront; return Result!Definition(definition); @@ -132,13 +222,13 @@ in (!tokens.empty, "Expected definition, got end of stream") return Result!Definition("Expected a number", tokens.front.position); } -private Result!Statement parseStatement(ref Array!Token.Range tokens) @nogc +private Result!Statement parseStatement(ref Token[] tokens) @nogc in (!tokens.empty, "Expected block, got end of stream") { - if (tokens.front.ofType(Token.Type.bang)) + if (tokens.front.of() == Token.Type.bang) { tokens.popFront; - auto statement = MmapPool.instance.make!BangStatement; + auto statement = defaultAllocator.make!BangStatement; auto expression = parseExpression(tokens); if (expression.valid) { @@ -153,7 +243,7 @@ in (!tokens.empty, "Expected block, got end of stream") return Result!Statement("Expected ! statement", tokens.front.position); } -private Result!(Array!Definition) parseDefinitions(ref Array!Token.Range tokens) @nogc +private Result!(Array!Definition) parseDefinitions(ref Token[] tokens) @nogc in (!tokens.empty, "Expected definition, got end of stream") { tokens.popFront; // Skip const. @@ -168,11 +258,11 @@ in (!tokens.empty, "Expected definition, got end of stream") return typeof(return)(definition.error.get); } definitions.insertBack(definition.result); - if (tokens.front.ofType(Token.Type.semicolon)) + if (tokens.front.of() == Token.Type.semicolon) { break; } - if (tokens.front.ofType(Token.Type.comma)) + if (tokens.front.of() == Token.Type.comma) { tokens.popFront; } @@ -181,49 +271,11 @@ in (!tokens.empty, "Expected definition, got end of stream") return typeof(return)(definitions); } -private Result!(Array!VariableDeclaration) parseVariableDeclarations(ref Array!Token.Range tokens) @nogc -in (!tokens.empty, "Expected variable declarations, got end of stream") -{ - tokens.popFront; // Skip var. - - Array!VariableDeclaration variableDeclarations; - - while (!tokens.empty) - { - auto currentToken = tokens.front; - if (currentToken.ofType(Token.Type.identifier)) - { - auto variableDeclaration = MmapPool.instance.make!VariableDeclaration; - variableDeclaration.identifier = currentToken.value!(Token.Type.identifier); - variableDeclarations.insertBack(variableDeclaration); - tokens.popFront; - } - else - { - return typeof(return)("Expected variable name", tokens.front.position); - } - if (tokens.empty) - { - return typeof(return)("Expected \";\" or \",\" name", currentToken.position); - } - if (tokens.front.ofType(Token.Type.semicolon)) - { - break; - } - if (tokens.front.ofType(Token.Type.comma)) - { - tokens.popFront; - } - } - - return typeof(return)(variableDeclarations); -} - -private Result!Block parseBlock(ref Array!Token.Range tokens) @nogc +private Result!Block parseBlock(ref Token[] tokens) @nogc in (!tokens.empty, "Expected block, got end of stream") { - auto block = MmapPool.instance.make!Block; - if (tokens.front.ofType(Token.Type.let)) + auto block = defaultAllocator.make!Block; + if (tokens.front.of() == Token.Type.let) { auto constDefinitions = parseDefinitions(tokens); if (constDefinitions.valid) @@ -236,19 +288,6 @@ in (!tokens.empty, "Expected block, got end of stream") } tokens.popFront; } - if (tokens.front.ofType(Token.Type.var)) - { - auto variableDeclarations = parseVariableDeclarations(tokens); - if (variableDeclarations.valid) - { - block.variableDeclarations = variableDeclarations.result; - } - else - { - return Result!Block(variableDeclarations.error.get); - } - tokens.popFront; - } auto statement = parseStatement(tokens); if (statement.valid) { @@ -262,7 +301,7 @@ in (!tokens.empty, "Expected block, got end of stream") return Result!Block(block); } -Result!Block parse(ref Array!Token tokenStream) @nogc +Result!Block parse(Token[] tokenStream) @nogc { auto tokens = tokenStream[]; return parseBlock(tokens); diff --git a/source/elna/result.d b/source/elna/result.d index 049c453..f35f68e 100644 --- a/source/elna/result.d +++ b/source/elna/result.d @@ -1,10 +1,13 @@ module elna.result; import std.typecons; +import tanya.container.array; +import tanya.container.string; /** * Position in the source text. */ +extern(C++, "elna") struct Position { /// Line. @@ -14,9 +17,10 @@ struct Position size_t column = 1; } +extern(C++, "elna") struct CompileError { - private string message_; + private const(char)* message_; private Position position_; @@ -27,29 +31,16 @@ struct CompileError * message = Error text. * position = Error position in the source text. */ - this(string message, Position position) @nogc nothrow pure @safe - { - this.message_ = message; - this.position_ = position; - } + this(const(char)* message, const Position position) @nogc nothrow pure @safe; /// Error text. - @property string message() const @nogc nothrow pure @safe - { - return this.message_; - } + @property const(char)* what() const @nogc nothrow pure @safe; /// Error line in the source text. - @property size_t line() const @nogc nothrow pure @safe - { - return this.position_.line; - } + @property size_t line() const @nogc nothrow pure @safe; /// Error column in the source text. - @property size_t column() const @nogc nothrow pure @safe - { - return this.position_.column; - } + @property size_t column() const @nogc nothrow pure @safe; } struct Result(T) @@ -63,7 +54,7 @@ struct Result(T) this.error = typeof(this.error).init; } - this(string message, Position position) + this(const(char)* message, Position position) { this.result = T.init; this.error = CompileError(message, position); @@ -82,3 +73,26 @@ struct Result(T) return error.isNull; } } + +extern(C++, "elna") +enum Target +{ + text, + high20, + lower12i +} + +extern(C++, "elna") +struct Reference +{ + const(char)* name; + size_t offset; + Target target; +} + +struct Symbol +{ + String name; + Array!ubyte text; + Array!Reference symbols; +} diff --git a/source/elna/riscv.d b/source/elna/riscv.d new file mode 100644 index 0000000..2719f48 --- /dev/null +++ b/source/elna/riscv.d @@ -0,0 +1,183 @@ +module elna.riscv; + +import core.stdc.stdlib; +import elna.ir; +import elna.result; +import tanya.container.array; +import tanya.container.string; + +extern(C++, "elna") +enum XRegister : ubyte +{ + zero = 0, + ra = 1, + sp = 2, + gp = 3, + tp = 4, + t0 = 5, + t1 = 6, + t2 = 7, + s0 = 8, + s1 = 9, + a0 = 10, + a1 = 11, + a2 = 12, + a3 = 13, + a4 = 14, + a5 = 15, + a6 = 16, + a7 = 17, + s2 = 18, + s3 = 19, + s4 = 20, + s5 = 21, + s6 = 22, + s7 = 23, + s8 = 24, + s9 = 25, + s10 = 26, + s11 = 27, + t3 = 28, + t4 = 29, + t5 = 30, + t6 = 31, +} + +extern(C++, "elna") +enum Funct3 : ubyte +{ + addi = 0b000, + slti = 0b001, + sltiu = 0b011, + andi = 0b111, + ori = 0b110, + xori = 0b100, + slli = 0b000, + srli = 0b101, + srai = 0b101, + add = 0b000, + slt = 0b010, + sltu = 0b011, + and = 0b111, + or = 0b110, + xor = 0b100, + sll = 0b001, + srl = 0b101, + sub = 0b000, + sra = 0b101, + beq = 0b000, + bne = 0b001, + blt = 0b100, + bltu = 0b110, + bge = 0b101, + bgeu = 0b111, + fence = 0b000, + fenceI = 0b001, + csrrw = 0b001, + csrrs = 0b010, + csrrc = 0b011, + csrrwi = 0b101, + csrrsi = 0b110, + csrrci = 0b111, + priv = 0b000, + sb = 0b000, + sh = 0b001, + sw = 0b010, + lb = 0b000, + lh = 0b001, + lw = 0b010, + lbu = 0b100, + lhu = 0b101, + jalr = 0b000, +} + +extern(C++, "elna") +enum Funct12 : ubyte +{ + ecall = 0b000000000000, + ebreak = 0b000000000001, +} + +extern(C++, "elna") +enum Funct7 : ubyte +{ + none = 0, + sub = 0b0100000 +} + +extern(C++, "elna") +enum BaseOpcode : ubyte +{ + opImm = 0b0010011, + lui = 0b0110111, + auipc = 0b0010111, + op = 0b0110011, + jal = 0b1101111, + jalr = 0b1100111, + branch = 0b1100011, + load = 0b0000011, + store = 0b0100011, + miscMem = 0b0001111, + system = 0b1110011, +} + +extern(C++, "elna") +struct Instruction +{ + private uint instruction; + + this(BaseOpcode opcode) @nogc; + @disable this(); + + ref Instruction i(XRegister rd, Funct3 funct3, XRegister rs1, uint immediate) + return scope @nogc; + + ref Instruction s(uint imm1, Funct3 funct3, XRegister rs1, XRegister rs2) + return scope @nogc; + + ref Instruction r(XRegister rd, Funct3 funct3, XRegister rs1, XRegister rs2, Funct7 funct7 = Funct7.none) + return scope @nogc; + + ref Instruction u(XRegister rd, uint imm) + return scope @nogc; + + ubyte* encode() return scope @nogc; +} + +extern(C++, "elna") +class RiscVVisitor : IRVisitor +{ + Instruction *instructions; + size_t instructionsLength; + bool registerInUse; + uint variableCounter = 1; + Reference[3] references; + + override void visit(Node) @nogc; + override void visit(Definition definition) @nogc; + override void visit(Operand operand) @nogc; + override void visit(Variable variable) @nogc; + override void visit(Number number) @nogc; + override void visit(BinaryExpression expression) @nogc; +} + +Symbol writeNext(Definition ast) @nogc +{ + Array!Instruction instructions; + auto visitor = cast(RiscVVisitor) malloc(__traits(classInstanceSize, RiscVVisitor)); + (cast(void*) visitor)[0 .. __traits(classInstanceSize, RiscVVisitor)] = __traits(initSymbol, RiscVVisitor)[]; + scope (exit) + { + free(cast(void*) visitor); + } + visitor.visit(ast); + + auto program = Symbol(String("main")); + + program.symbols = Array!Reference(visitor.references[]); + foreach (ref instruction; visitor.instructions[0 .. visitor.instructionsLength]) + { + program.text.insertBack(instruction.encode[0 .. uint.sizeof]); + } + return program; +} diff --git a/source/ir.cpp b/source/ir.cpp new file mode 100644 index 0000000..f7ae9ff --- /dev/null +++ b/source/ir.cpp @@ -0,0 +1,53 @@ +#include "elna/ir.hpp" + +namespace elna::ir +{ + /** + * AST node. + */ + void Node::accept(IRVisitor *) + { + } + + void Definition::accept(IRVisitor *visitor) + { + visitor->visit(this); + } + + void Operand::accept(IRVisitor *visitor) + { + visitor->visit(this); + } + + void Number::accept(IRVisitor *visitor) + { + visitor->visit(this); + } + + void Variable::accept(IRVisitor *visitor) + { + visitor->visit(this); + } + + BinaryExpression::BinaryExpression(Operand *lhs, Operand *rhs, BinaryOperator _operator) + { + this->lhs = lhs; + this->rhs = rhs; + this->_operator = _operator; + } + + void BinaryExpression::accept(IRVisitor *visitor) + { + visitor->visit(this); + } + + BangExpression::BangExpression(Operand *operand) + { + this->operand = operand; + } + + void BangExpression::accept(IRVisitor *visitor) + { + visitor->visit(this); + } +} diff --git a/source/lexer.cpp b/source/lexer.cpp new file mode 100644 index 0000000..00de021 --- /dev/null +++ b/source/lexer.cpp @@ -0,0 +1,270 @@ +#include "elna/lexer.hpp" + +namespace elna +{ + source::source(const std::string& buffer) + : m_buffer(buffer) + { + } + + source::const_iterator source::begin() const + { + return source::const_iterator(std::cbegin(m_buffer)); + } + + source::const_iterator source::end() const + { + Position end_position{ 0, 0 }; + + return source::const_iterator(std::cend(m_buffer), end_position); + } + + source::const_iterator::const_iterator(std::string::const_iterator buffer, + const Position start_position) + : m_buffer(buffer), m_position(start_position) + { + } + + const Position& source::const_iterator::position() const noexcept + { + return this->m_position; + } + + source::const_iterator::reference source::const_iterator::operator*() const noexcept + { + return *m_buffer; + } + + source::const_iterator::pointer source::const_iterator::operator->() const noexcept + { + return m_buffer.base(); + } + + source::const_iterator& source::const_iterator::operator++() + { + if (*this->m_buffer == '\n') + { + this->m_position.column = 1; + ++this->m_position.line; + } + else + { + ++this->m_position.column; + } + std::advance(this->m_buffer, 1); + + return *this; + } + + source::const_iterator& source::const_iterator::operator++(int) + { + auto tmp = *this; + ++(*this); + return *this; + } + + bool source::const_iterator::operator==(const source::const_iterator& that) const noexcept + { + return this->m_buffer == that.m_buffer; + } + + bool source::const_iterator::operator!=(const source::const_iterator& that) const noexcept + { + return !(*this == that); + } + + Token::Token(const Type of, const char *value, Position position) + : m_type(of), m_position(position) + { + std::size_t value_length = strlen(value); + char *buffer = reinterpret_cast(malloc(value_length + 1)); + + std::memcpy(buffer, value, value_length); + buffer[value_length] = 0; + + m_value.identifier = buffer; + } + + Token::Token(const Type of, std::int32_t number, Position position) + : m_type(of), m_position(position) + { + m_value.number = number; + } + + Token::Token(const Type of, Position position) + : m_type(of), m_position(position) + { + } + + Token::Token(const Token& that) + : m_type(that.of()), m_position(that.position()) + { + *this = that; + } + + Token::Token(Token&& that) + : m_type(that.of()), m_position(that.position()) + { + *this = std::move(that); + } + + Token::~Token() + { + if (m_type == TOKEN_IDENTIFIER || m_type == TOKEN_OPERATOR) + { + std::free(const_cast(m_value.identifier)); + } + } + + Token& Token::operator=(const Token& that) + { + m_type = that.of(); + m_position = that.position(); + if (that.of() == TOKEN_IDENTIFIER || that.of() == TOKEN_OPERATOR) + { + std::size_t value_length = strlen(that.identifier()); + char *buffer = reinterpret_cast(malloc(value_length + 1)); + + std::memcpy(buffer, that.identifier(), value_length); + buffer[value_length] = 0; + + m_value.identifier = buffer; + } + else if (that.of() == TOKEN_NUMBER) + { + m_value.number = that.number(); + } + return *this; + } + + Token& Token::operator=(Token&& that) + { + m_type = that.of(); + m_position = that.position(); + if (that.of() == TOKEN_IDENTIFIER || that.of() == TOKEN_OPERATOR) + { + m_value.identifier = that.identifier(); + that.m_value.identifier = nullptr; + } + else if (that.of() == TOKEN_NUMBER) + { + m_value.number = that.number(); + } + return *this; + } + + Token::Type Token::of() const noexcept + { + return m_type; + } + + const char *Token::identifier() const noexcept + { + return m_value.identifier; + } + + std::int32_t Token::number() const noexcept + { + return m_value.number; + } + + const Position& Token::position() const noexcept + { + return m_position; + } + + Token *lex(const char *buffer, CompileError *compile_error, std::size_t *length) + { + std::vector tokens; + source input{ buffer }; + + for (auto iterator = input.begin(); iterator != input.end();) + { + if (*iterator == ' ' || *iterator == '\n') + { + } + else if (std::isdigit(*iterator)) + { + tokens.emplace_back( + Token::TOKEN_NUMBER, + static_cast(*iterator - '0'), + iterator.position() + ); + } + else if (*iterator == '=') + { + tokens.emplace_back(Token::TOKEN_EQUALS, iterator.position()); + } + else if (*iterator == '(') + { + tokens.emplace_back(Token::TOKEN_LEFT_PAREN, iterator.position()); + } + else if (*iterator == ')') + { + tokens.emplace_back(Token::TOKEN_RIGHT_PAREN, iterator.position()); + } + else if (*iterator == ';') + { + tokens.emplace_back(Token::TOKEN_SEMICOLON, iterator.position()); + } + else if (*iterator == ',') + { + tokens.emplace_back(Token::TOKEN_COMMA, iterator.position()); + } + else if (*iterator == '!') + { + tokens.emplace_back(Token::TOKEN_BANG, iterator.position()); + } + else if (*iterator == '.') + { + tokens.emplace_back(Token::TOKEN_DOT, iterator.position()); + } + else if (std::isalpha(*iterator)) + { + std::string word; + auto i = iterator; + while (i != input.end() && std::isalpha(*i)) + { + word.push_back(*i); + ++i; + } + if (word == "const") + { + tokens.emplace_back(Token::TOKEN_LET, iterator.position()); + } + else if (word == "var") + { + tokens.emplace_back(Token::TOKEN_VAR, iterator.position()); + } + else + { + tokens.emplace_back(Token::TOKEN_IDENTIFIER, word.c_str(), iterator.position()); + } + iterator = i; + continue; + } + else if (*iterator == '+' || *iterator == '-') + { + std::string _operator{ *iterator }; + + tokens.emplace_back(Token::TOKEN_OPERATOR, _operator.c_str(), iterator.position()); + } + else + { + *compile_error = CompileError("Unexpected next character", iterator.position()); + return nullptr; + } + ++iterator; + } + Token *target = reinterpret_cast(malloc(tokens.size() * sizeof(Token) + sizeof(Token))); + int i = 0; + for (auto& token : tokens) + { + target[i] = std::move(token); + ++i; + } + *length = i; + + return target; + } +} diff --git a/source/main.d b/source/main.d index 71d23cd..dcb9fa0 100644 --- a/source/main.d +++ b/source/main.d @@ -1,72 +1,33 @@ -import core.stdc.stdio; -import core.stdc.string; -import core.stdc.stdlib; -import elna.lexer; -import elna.parser; -import elna.generator; +import elna.backend; import elna.ir; +import elna.arguments; +import std.path; +import std.sumtype; import tanya.container.string; import tanya.memory.allocator; import tanya.memory.mmappool; -private char[] readSource(size_t N)(string source, out char[N] buffer) @nogc -{ - memcpy(buffer.ptr, source.ptr, source.length + 1); - buffer[source.length] = '\0'; - auto handle = fopen(buffer.ptr, "r"); - if (handle is null) - { - perror(buffer.ptr); - return null; - } - fseek(handle, 0, SEEK_END); - size_t fsize = ftell(handle); - rewind(handle); - - fread(buffer.ptr, fsize, 1, handle); - fclose(handle); - buffer[fsize] = '\0'; - - return buffer[0 .. fsize]; -} - int main(string[] args) { - char[255] buffer; - defaultAllocator = MmapPool.instance; - if (args.length < 2) - { - return 4; - } - auto sourceText = readSource(args[1], buffer); - if (sourceText is null) - { - return 3; - } - auto tokens = lex(sourceText); - if (tokens.length == 0) - { - printf("Lexical analysis failed.\n"); - return 1; - } - auto ast = parse(tokens); - if (!ast.valid) - { - auto compileError = ast.error.get; - printf("%lu:%lu: %s\n", compileError.line, compileError.column, compileError.message.ptr); - return 2; - } - auto ir = transform(ast.result); + return Arguments.parse(args).match!( + (ArgumentError argumentError) => 4, + (Arguments arguments) { + String outputFilename; + if (arguments.output is null) + { + outputFilename = arguments + .inFile + .baseName + .withExtension("o"); + } + else + { + outputFilename = String(arguments.output); + } - String outputFilename = String("build/"); - outputFilename.insertBack(args[1][0 .. $ - 4]); - outputFilename.insertBack("o"); - writeObject(ir, outputFilename); - - auto code = generate(ir); - printf("%s", code.toStringz()); - - return 0; + return generate(arguments.inFile, outputFilename); + } + ); } diff --git a/source/result.cpp b/source/result.cpp new file mode 100644 index 0000000..c027c70 --- /dev/null +++ b/source/result.cpp @@ -0,0 +1,25 @@ +#include "elna/result.hpp" + +namespace elna +{ + CompileError::CompileError(const char *message, const Position position) noexcept + { + this->message = message; + this->position = position; + } + + char const *CompileError::what() const noexcept + { + return this->message; + } + + std::size_t CompileError::line() const noexcept + { + return this->position.line; + } + + std::size_t CompileError::column() const noexcept + { + return this->position.column; + } +} diff --git a/source/riscv.cpp b/source/riscv.cpp new file mode 100644 index 0000000..c8d434f --- /dev/null +++ b/source/riscv.cpp @@ -0,0 +1,191 @@ +#include "elna/parser.hpp" +#include "elna/riscv.hpp" +#include + +namespace elna +{ + Instruction::Instruction(BaseOpcode opcode) + { + this->instruction = static_cast::type>(opcode); + } + + Instruction& Instruction::i(XRegister rd, Funct3 funct3, XRegister rs1, std::uint32_t immediate) + { + this->instruction |= (static_cast::type>(rd) << 7) + | (static_cast::type>(funct3) << 12) + | (static_cast::type>(rs1) << 15) + | (immediate << 20); + + return *this; + } + + Instruction& Instruction::s(std::uint32_t imm1, Funct3 funct3, XRegister rs1, XRegister rs2) + { + this->instruction |= ((imm1 & 0b11111) << 7) + | (static_cast::type>(funct3) << 12) + | (static_cast::type>(rs1) << 15) + | (static_cast::type>(rs2) << 20) + | ((imm1 & 0b111111100000) << 20); + + return *this; + } + + Instruction& Instruction::r(XRegister rd, Funct3 funct3, XRegister rs1, XRegister rs2, Funct7 funct7) + { + this->instruction |= (static_cast::type>(rd) << 7) + | (static_cast::type>(funct3) << 12) + | (static_cast::type>(rs1) << 15) + | (static_cast::type>(rs2) << 20) + | (static_cast::type>(funct7) << 25); + + return *this; + } + + Instruction& Instruction::u(XRegister rd, std::uint32_t imm) + { + this->instruction |= (static_cast::type>(rd) << 7) | (imm << 12); + + return *this; + } + + std::uint8_t *Instruction::encode() + { + return reinterpret_cast(&this->instruction); + } + + void RiscVVisitor::visit(ir::Node *) + { + } + + void RiscVVisitor::visit(ir::Definition *definition) + { + const uint stackSize = static_cast(definition->statementsLength * 4 + 12); + + this->instructionsLength += 4; + this->instructions = reinterpret_cast( + realloc(this->instructions, this->instructionsLength * sizeof(Instruction))); + + // Prologue. + this->instructions[instructionsLength - 4] = Instruction(BaseOpcode::opImm) + .i(XRegister::sp, Funct3::addi, XRegister::sp, -stackSize); + this->instructions[instructionsLength - 3] = Instruction(BaseOpcode::store) + .s(stackSize - 4, Funct3::sw, XRegister::sp, XRegister::s0); + this->instructions[instructionsLength - 2] = Instruction(BaseOpcode::store) + .s(stackSize - 8, Funct3::sw, XRegister::sp, XRegister::ra); + this->instructions[instructionsLength - 1] = Instruction(BaseOpcode::opImm) + .i(XRegister::s0, Funct3::addi, XRegister::sp, stackSize); + + for (std::size_t i = 0; i < definition->statementsLength; ++i) + { + definition->statements[i]->accept(this); + } + this->registerInUse = true; + definition->result->accept(this); + this->registerInUse = false; + + this->instructions = reinterpret_cast( + realloc(this->instructions, (this->instructionsLength + 10) * sizeof(Instruction))); + + // Print the result. + this->instructions[instructionsLength++] = Instruction(BaseOpcode::opImm) + .i(XRegister::a1, Funct3::addi, XRegister::a0, 0); + this->references[0] = Reference(); + this->references[0].name = ".CL0"; + this->references[0].offset = instructionsLength * 4; + this->references[0].target = Target::high20; + this->instructions[instructionsLength++] = Instruction(BaseOpcode::lui).u(XRegister::a5, 0); + this->references[1] = Reference(); + this->references[1].name = ".CL0"; + this->references[1].offset = instructionsLength * 4; + this->references[1].target = Target::lower12i; + + this->instructions[instructionsLength++] = Instruction(BaseOpcode::opImm) + .i(XRegister::a0, Funct3::addi, XRegister::a5, 0); + this->references[2] = Reference(); + this->references[2].name = "printf"; + this->references[2].offset = instructionsLength * 4; + this->references[2].target = Target::text; + this->instructions[instructionsLength++] = Instruction(BaseOpcode::auipc).u(XRegister::ra, 0); + this->instructions[instructionsLength++] = Instruction(BaseOpcode::jalr) + .i(XRegister::ra, Funct3::jalr, XRegister::ra, 0); + // Set the return value (0). + this->instructions[instructionsLength++] = Instruction(BaseOpcode::op) + .r(XRegister::a0, Funct3::_and, XRegister::zero, XRegister::zero); + + // Epilogue. + this->instructions[instructionsLength++] = Instruction(BaseOpcode::load) + .i(XRegister::s0, Funct3::lw, XRegister::sp, stackSize - 4); + this->instructions[instructionsLength++] = Instruction(BaseOpcode::load) + .i(XRegister::ra, Funct3::lw, XRegister::sp, stackSize - 8); + this->instructions[instructionsLength++] = Instruction(BaseOpcode::opImm) + .i(XRegister::sp, Funct3::addi, XRegister::sp, stackSize); + this->instructions[instructionsLength++] = Instruction(BaseOpcode::jalr) + .i(XRegister::zero, Funct3::jalr, XRegister::ra, 0); + } + + void RiscVVisitor::visit(ir::Operand *operand) + { + if (dynamic_cast(operand) != nullptr) + { + return dynamic_cast(operand)->accept(this); + } + if (dynamic_cast(operand) != nullptr) + { + return dynamic_cast(operand)->accept(this); + } + } + + void RiscVVisitor::visit(ir::Variable *variable) + { + const auto freeRegister = this->registerInUse ? XRegister::a0 : XRegister::t0; + + ++this->instructionsLength; + this->instructions = reinterpret_cast( + realloc(this->instructions, this->instructionsLength * sizeof(Instruction))); + // movl -x(%rbp), %eax; where x is a number. + this->instructions[instructionsLength - 1] = Instruction(BaseOpcode::load) + .i(freeRegister, Funct3::lw, XRegister::sp, + static_cast(variable->counter * 4)); + } + + void RiscVVisitor::visit(ir::Number *number) + { + const auto freeRegister = this->registerInUse ? XRegister::a0 : XRegister::t0; + + ++this->instructionsLength; + this->instructions = reinterpret_cast( + realloc(this->instructions, this->instructionsLength * sizeof(Instruction))); + this->instructions[this->instructionsLength - 1] = + Instruction(BaseOpcode::opImm) // movl $x, %eax; where $x is a number. + .i(freeRegister, Funct3::addi, XRegister::zero, number->value); + } + + void RiscVVisitor::visit(ir::BinaryExpression *expression) + { + this->registerInUse = true; + expression->lhs->accept(this); + this->registerInUse = false; + expression->rhs->accept(this); + + this->instructionsLength += 2; + this->instructions = reinterpret_cast( + realloc(this->instructions, this->instructionsLength * sizeof(Instruction))); + // Calculate the result and assign it to a variable on the stack. + switch (expression->_operator) + { + case BinaryOperator::sum: + this->instructions[instructionsLength - 2] = Instruction(BaseOpcode::op) + .r(XRegister::a0, Funct3::add, XRegister::a0, XRegister::t0); + break; + case BinaryOperator::subtraction: + this->instructions[instructionsLength - 2] = Instruction(BaseOpcode::op) + .r(XRegister::a0, Funct3::sub, XRegister::a0, XRegister::t0, Funct7::sub); + break; + } + this->instructions[instructionsLength - 1] = // movl %eax, -x(%rbp); where x is a number. + Instruction(BaseOpcode::store) + .s(static_cast(this->variableCounter * 4), Funct3::sw, XRegister::sp, XRegister::a0); + + ++this->variableCounter; + } +} diff --git a/tests/7_member_sum.eln b/tests/7_member_sum.eln new file mode 100644 index 0000000..a54a60a --- /dev/null +++ b/tests/7_member_sum.eln @@ -0,0 +1 @@ +! 3 + 4 + 5 + 1 + 2 + 4 + 3 diff --git a/tests/const_list.elna b/tests/const_list.eln similarity index 73% rename from tests/const_list.elna rename to tests/const_list.eln index 18a6711..375627f 100644 --- a/tests/const_list.elna +++ b/tests/const_list.eln @@ -1,3 +1,3 @@ const a = 1, b = 2; -! + a b +! a + b . diff --git a/tests/expectations/7_member_sum.txt b/tests/expectations/7_member_sum.txt new file mode 100644 index 0000000..2bd5a0a --- /dev/null +++ b/tests/expectations/7_member_sum.txt @@ -0,0 +1 @@ +22 diff --git a/tests/expectations/left_nested_sum.txt b/tests/expectations/left_nested_sum.txt new file mode 100644 index 0000000..45a4fb7 --- /dev/null +++ b/tests/expectations/left_nested_sum.txt @@ -0,0 +1 @@ +8 diff --git a/tests/expectations/print_number.txt b/tests/expectations/print_number.txt new file mode 100644 index 0000000..00750ed --- /dev/null +++ b/tests/expectations/print_number.txt @@ -0,0 +1 @@ +3 diff --git a/tests/expectations/subtraction.txt b/tests/expectations/subtraction.txt new file mode 100644 index 0000000..d00491f --- /dev/null +++ b/tests/expectations/subtraction.txt @@ -0,0 +1 @@ +1 diff --git a/tests/left_nested_sum.eln b/tests/left_nested_sum.eln new file mode 100644 index 0000000..aae2a77 --- /dev/null +++ b/tests/left_nested_sum.eln @@ -0,0 +1,2 @@ +! (3 + 4) + 1 +. diff --git a/tests/print_number.eln b/tests/print_number.eln new file mode 100644 index 0000000..51b5ca0 --- /dev/null +++ b/tests/print_number.eln @@ -0,0 +1,2 @@ +! 3 +. diff --git a/tests/runner.cpp b/tests/runner.cpp new file mode 100755 index 0000000..27c6052 --- /dev/null +++ b/tests/runner.cpp @@ -0,0 +1,151 @@ +#include + +#include +#include +#include "elna/tester.hpp" + +namespace elna +{ + std::uint32_t test_results::total() const noexcept + { + return m_total; + } + + std::uint32_t test_results::passed() const noexcept + { + return m_passed; + } + + std::uint32_t test_results::failed() const noexcept + { + return m_total - m_passed; + } + + int test_results::exit_code() const noexcept + { + return m_total == m_passed ? EXIT_SUCCESS : EXIT_FAILURE; + } + + void test_results::add_exit_code(const int exit_code) noexcept + { + ++m_total; + if (exit_code == 0) + { + ++m_passed; + } + } + + static std::string in_build_directory(const std::filesystem::path& path) + { + return "build/riscv" / path; + } + + static int build_test(const std::filesystem::directory_entry& test_entry) + { + const std::filesystem::path test_filename = test_entry.path().filename(); + + std::filesystem::path test_binary = in_build_directory(test_filename); + std::filesystem::path test_object = test_binary; + test_binary.replace_extension(); + test_object.replace_extension(".o"); + + std::filesystem::remove(test_binary); + std::filesystem::remove(test_object); + + auto status = boost::process::system("./build/bin/elna", + "-o", test_object.string(), test_entry.path().string()); + if (status != 0) + { + return status; + } + status = boost::process::system( + "/opt/riscv/bin/riscv32-unknown-elf-ld", + "-o", test_binary.string(), + "-L/opt/riscv/lib/gcc/riscv32-unknown-elf/13.2.0/", + "-L/opt/riscv/riscv32-unknown-elf/lib", + "/opt/riscv/riscv32-unknown-elf/lib/crt0.o", + "/opt/riscv/lib/gcc/riscv32-unknown-elf/13.2.0/crtbegin.o", + test_object.string(), + "--start-group", "-lgcc", "-lc", "-lgloss", "--end-group", + "/opt/riscv/lib/gcc/riscv32-unknown-elf/13.2.0/crtend.o" + ); + return status; + } + + static 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 = in_build_directory(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(); + } + + static 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; + } + int status{ 0 }; + + try + { + status = build_test(test_entry); + } + catch (const boost::process::process_error& exception) + { + std::cout << exception.what() << std::endl; + status = 3; + continue; + } + if (status == 0) + { + status = run_test(test_entry); + } + results.add_exit_code(status); + } + 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 = elna::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(); +} diff --git a/tests/subtraction.eln b/tests/subtraction.eln new file mode 100644 index 0000000..00917c6 --- /dev/null +++ b/tests/subtraction.eln @@ -0,0 +1,2 @@ +! 5 - 4 +. diff --git a/tests/sum.eln b/tests/sum.eln new file mode 100644 index 0000000..6b4e720 --- /dev/null +++ b/tests/sum.eln @@ -0,0 +1,2 @@ +! 1 + 7 +. diff --git a/tests/sum.elna b/tests/sum.elna deleted file mode 100644 index 12343f0..0000000 --- a/tests/sum.elna +++ /dev/null @@ -1,2 +0,0 @@ -! + 1 7 -. diff --git a/tests/sums.eln b/tests/sums.eln new file mode 100644 index 0000000..59586e9 --- /dev/null +++ b/tests/sums.eln @@ -0,0 +1,2 @@ +! 1 + (3 + 4) +. diff --git a/tests/sums.elna b/tests/sums.elna deleted file mode 100644 index bf80ecc..0000000 --- a/tests/sums.elna +++ /dev/null @@ -1,2 +0,0 @@ -! + 1 (+ 3 4) -.