diff --git a/CMakeLists.txt b/CMakeLists.txt index 6f459f8..8279c8a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -64,6 +64,7 @@ if(BUILD_TESTING) ${CMAKE_CURRENT_SOURCE_DIR}/tests/lexer_exact_test.cpp ${CMAKE_CURRENT_SOURCE_DIR}/tests/parser_exact_test.cpp ${CMAKE_CURRENT_SOURCE_DIR}/tests/parser_syntax_error_files_test.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/tests/ir_constant_folding_test.cpp ${CMAKE_CURRENT_SOURCE_DIR}/tests/symbol_table_test.cpp ) target_link_libraries(minijava_tests PRIVATE GTest::gtest_main minijava_core) diff --git a/src/bytecode/BytecodeInstruction.cpp b/src/bytecode/BytecodeInstruction.cpp index 6d53101..45cfdab 100644 --- a/src/bytecode/BytecodeInstruction.cpp +++ b/src/bytecode/BytecodeInstruction.cpp @@ -15,7 +15,7 @@ void StackParameterInstruction::serialize(Serializer &serializer) const { }; void IntegerParameterInstruction::serialize(Serializer &serializer) const { serializer.writeOpcode(opcode); - serializer.writeInteger(param); + serializer.writeSignedInteger(param); }; void StringParameterInstruction::serialize(Serializer &serializer) const { serializer.writeOpcode(opcode); diff --git a/src/bytecode/BytecodeInstruction.hpp b/src/bytecode/BytecodeInstruction.hpp index d74f000..656dbc1 100644 --- a/src/bytecode/BytecodeInstruction.hpp +++ b/src/bytecode/BytecodeInstruction.hpp @@ -15,9 +15,8 @@ class BytecodeInstruction { public: BytecodeInstruction(Opcode opcode_) - : opcode(opcode_), - mnemonic(mnemonics.at( - static_cast(static_cast(opcode_)))) {}; + : opcode(opcode_), mnemonic(mnemonics.at(static_cast( + static_cast(opcode_)))) {}; virtual ~BytecodeInstruction() = default; virtual void print(std::ostream &os) const = 0; @@ -39,13 +38,14 @@ class StackParameterInstruction : public BytecodeInstruction { class IntegerParameterInstruction : public BytecodeInstruction { // An instruction which takes one integer parameter // and pushes one result back to the stack - size_t param; + std::int64_t param; public: - IntegerParameterInstruction(Opcode opcode_, size_t param_) + IntegerParameterInstruction(Opcode opcode_, std::int64_t param_) : BytecodeInstruction(opcode_), param(param_) {}; void print(std::ostream &os) const override; void serialize(Serializer &serializer) const override; + [[nodiscard]] std::int64_t getParam() const { return param; } }; class StringParameterInstruction : public BytecodeInstruction { diff --git a/src/bytecode/BytecodeMethod.hpp b/src/bytecode/BytecodeMethod.hpp index 5a65632..a856fac 100644 --- a/src/bytecode/BytecodeMethod.hpp +++ b/src/bytecode/BytecodeMethod.hpp @@ -34,6 +34,7 @@ class BytecodeMethod { void print(std::ostream &os) const; + [[nodiscard]] const auto &getBlocks() const { return blocks; } [[nodiscard]] const auto &getVariables() const { return variables; } [[nodiscard]] const auto &getFieldVariables() const { return fieldVariables; diff --git a/src/bytecode/BytecodeMethodBlock.cpp b/src/bytecode/BytecodeMethodBlock.cpp index e345e3e..baf12c8 100644 --- a/src/bytecode/BytecodeMethodBlock.cpp +++ b/src/bytecode/BytecodeMethodBlock.cpp @@ -19,8 +19,7 @@ void BytecodeMethodBlock::addBytecodeInstruction(BytecodeInstruction *instr) { BytecodeMethodBlock &BytecodeMethodBlock::push(const Operand &operand) { if (const auto *ptr = std::get_if(&operand)) { addBytecodeInstruction( - new IntegerParameterInstruction(Opcode::CONST, - static_cast(*ptr))); + new IntegerParameterInstruction(Opcode::CONST, *ptr)); } else if (const auto *ptr = std::get_if(&operand)) { addBytecodeInstruction( new StringParameterInstruction(Opcode::LOAD, *ptr)); diff --git a/src/bytecode/BytecodeMethodBlock.hpp b/src/bytecode/BytecodeMethodBlock.hpp index 23f7362..7846772 100644 --- a/src/bytecode/BytecodeMethodBlock.hpp +++ b/src/bytecode/BytecodeMethodBlock.hpp @@ -18,6 +18,7 @@ class BytecodeMethodBlock { BytecodeMethodBlock(const std::string &name_) : name(name_) {}; [[nodiscard]] const std::string &getName() const { return name; } + [[nodiscard]] const auto &getInstructions() const { return instructions; } void print(std::ostream &os) const; void addBytecodeInstruction(BytecodeInstruction *instr); diff --git a/src/bytecode/BytecodeProgram.cpp b/src/bytecode/BytecodeProgram.cpp index 5417760..dde53d0 100644 --- a/src/bytecode/BytecodeProgram.cpp +++ b/src/bytecode/BytecodeProgram.cpp @@ -27,6 +27,19 @@ BytecodeMethod &BytecodeProgram::getBytecodeMethod(const std::string &name) { return *it; } +std::vector +BytecodeProgram::getInstructions() const { + std::vector instructions; + for (const auto &method : methods) { + for (const auto &block : method.getBlocks()) { + for (const auto &instruction : block.getInstructions()) { + instructions.push_back(instruction.get()); + } + } + } + return instructions; +} + void BytecodeProgram::print(std::ostream &os) const { for (const auto &method : methods) { method.print(os); diff --git a/src/bytecode/BytecodeProgram.hpp b/src/bytecode/BytecodeProgram.hpp index 38c351e..0d0fe97 100644 --- a/src/bytecode/BytecodeProgram.hpp +++ b/src/bytecode/BytecodeProgram.hpp @@ -16,6 +16,8 @@ class BytecodeProgram { std::vector fieldVariables); [[nodiscard]] BytecodeMethod &getBytecodeMethod(const std::string &name); + [[nodiscard]] std::vector + getInstructions() const; void print(std::ostream &os) const; diff --git a/src/ir/BBlock.hpp b/src/ir/BBlock.hpp index b8a21a1..d189cc9 100644 --- a/src/ir/BBlock.hpp +++ b/src/ir/BBlock.hpp @@ -37,6 +37,8 @@ class BBlock { BBlock *getFalseBlock() { return falseExit; } void addInstruction(Tac *ptr); + [[nodiscard]] const auto &getInstructions() const { return instructions; } + [[nodiscard]] auto &getInstructions() { return instructions; } void printBlockGraphviz(std::ostream &os); diff --git a/src/ir/CFG.hpp b/src/ir/CFG.hpp index 5827234..002c2bc 100644 --- a/src/ir/CFG.hpp +++ b/src/ir/CFG.hpp @@ -39,6 +39,7 @@ class CFG { [[nodiscard]] BBlock *addMethodBlock(); [[nodiscard]] BBlock *addMethodRootBlock(const std::string &className, const std::string &methodName); + [[nodiscard]] const auto &getMethodRoots() const { return methodRoots; } void setTypeInfo(const TypeInfo *info) { type_info_ = info; } [[nodiscard]] const std::string *typeOf(const Node &node) const; diff --git a/src/ir/Tac.hpp b/src/ir/Tac.hpp index 1a78cd6..99fbbd4 100644 --- a/src/ir/Tac.hpp +++ b/src/ir/Tac.hpp @@ -29,6 +29,21 @@ class Tac { virtual void generateBytecode([[maybe_unused]] BytecodeMethodBlock &block) { }; + [[nodiscard]] const std::string &getResult() const { return result; } + [[nodiscard]] const Operand &getLhsOperand() const { return lhsOp; } + [[nodiscard]] const Operand &getRhsOperand() const { return rhsOp; } + [[nodiscard]] const std::string &getOperator() const { return op; } + + void setResult(const std::string &value) { result = value; } + void setLhsOperand(const Operand &value) { + lhsOp = value; + lhs = to_string(lhsOp); + } + void setRhsOperand(const Operand &value) { + rhsOp = value; + rhs = to_string(rhsOp); + } + Tac(const std::string &result_) : result{result_} {} Tac(const std::string &result_, const Operand &lhs_, const std::string &op_, const Operand &rhs_) diff --git a/src/ir/passes/ConstantFoldingPass.cpp b/src/ir/passes/ConstantFoldingPass.cpp new file mode 100644 index 0000000..883bb2b --- /dev/null +++ b/src/ir/passes/ConstantFoldingPass.cpp @@ -0,0 +1,320 @@ +#include "ir/passes/ConstantFoldingPass.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ir/ArithmeticTac.hpp" +#include "ir/BBlock.hpp" +#include "ir/BooleanTac.hpp" +#include "ir/CFG.hpp" +#include "ir/LogicalTac.hpp" +#include "ir/Tac.hpp" + +namespace { +using ConstantEnvironment = std::unordered_map; + +[[nodiscard]] std::optional to_ir_immediate(std::int64_t value) { + if (value < std::numeric_limits::min() || + value > std::numeric_limits::max()) { + return std::nullopt; + } + return static_cast(value); +} + +[[nodiscard]] std::optional +resolve_constant_operand(const Operand &operand, + const ConstantEnvironment &environment) { + if (const auto *immediate = std::get_if(&operand)) { + return *immediate; + } + if (const auto *name = std::get_if(&operand)) { + if (const auto it = environment.find(*name); it != environment.end()) { + return it->second; + } + } + return std::nullopt; +} + +bool substitute_lhs_if_constant(Tac &instruction, + const ConstantEnvironment &environment) { + const auto *name = std::get_if(&instruction.getLhsOperand()); + if (name == nullptr) { + return false; + } + const auto it = environment.find(*name); + if (it == environment.end()) { + return false; + } + const auto value = to_ir_immediate(it->second); + if (!value.has_value()) { + return false; + } + instruction.setLhsOperand(*value); + return true; +} + +bool substitute_rhs_if_constant(Tac &instruction, + const ConstantEnvironment &environment) { + const auto *name = std::get_if(&instruction.getRhsOperand()); + if (name == nullptr) { + return false; + } + const auto it = environment.find(*name); + if (it == environment.end()) { + return false; + } + const auto value = to_ir_immediate(it->second); + if (!value.has_value()) { + return false; + } + instruction.setRhsOperand(*value); + return true; +} + +template +[[nodiscard]] std::optional +fold_binary(const Tac &instruction, const ConstantEnvironment &environment, + Operator op) { + const auto lhs = + resolve_constant_operand(instruction.getLhsOperand(), environment); + const auto rhs = + resolve_constant_operand(instruction.getRhsOperand(), environment); + if (!lhs.has_value() || !rhs.has_value()) { + return std::nullopt; + } + return op(*lhs, *rhs); +} + +[[nodiscard]] std::optional +try_fold_instruction(const Tac &instruction, + const ConstantEnvironment &environment) { + if (dynamic_cast(&instruction) != nullptr) { + return fold_binary( + instruction, environment, + [](std::int64_t lhs, std::int64_t rhs) { return lhs + rhs; }); + } + if (dynamic_cast(&instruction) != nullptr) { + return fold_binary( + instruction, environment, + [](std::int64_t lhs, std::int64_t rhs) { return lhs - rhs; }); + } + if (dynamic_cast(&instruction) != nullptr) { + return fold_binary( + instruction, environment, + [](std::int64_t lhs, std::int64_t rhs) { return lhs * rhs; }); + } + if (dynamic_cast(&instruction) != nullptr) { + const auto lhs = + resolve_constant_operand(instruction.getLhsOperand(), environment); + const auto rhs = + resolve_constant_operand(instruction.getRhsOperand(), environment); + if (!lhs.has_value() || !rhs.has_value() || *rhs == 0) { + return std::nullopt; + } + return *lhs / *rhs; + } + if (dynamic_cast(&instruction) != nullptr) { + return fold_binary(instruction, environment, + [](std::int64_t lhs, std::int64_t rhs) { + return lhs < rhs ? 1 : 0; + }); + } + if (dynamic_cast(&instruction) != nullptr) { + return fold_binary(instruction, environment, + [](std::int64_t lhs, std::int64_t rhs) { + return lhs > rhs ? 1 : 0; + }); + } + if (dynamic_cast(&instruction) != nullptr) { + return fold_binary(instruction, environment, + [](std::int64_t lhs, std::int64_t rhs) { + return lhs == rhs ? 1 : 0; + }); + } + if (dynamic_cast(&instruction) != nullptr) { + return fold_binary(instruction, environment, + [](std::int64_t lhs, std::int64_t rhs) { + return (lhs != 0 && rhs != 0) ? 1 : 0; + }); + } + if (dynamic_cast(&instruction) != nullptr) { + return fold_binary(instruction, environment, + [](std::int64_t lhs, std::int64_t rhs) { + return (lhs != 0 || rhs != 0) ? 1 : 0; + }); + } + if (dynamic_cast(&instruction) != nullptr) { + const auto rhs = + resolve_constant_operand(instruction.getRhsOperand(), environment); + if (!rhs.has_value()) { + return std::nullopt; + } + return *rhs == 0 ? 1 : 0; + } + return std::nullopt; +} + +bool substitute_constants_in_instruction( + Tac &instruction, const ConstantEnvironment &environment) { + bool changed = false; + + if (dynamic_cast(&instruction) != nullptr || + dynamic_cast(&instruction) != nullptr || + dynamic_cast(&instruction) != nullptr || + dynamic_cast(&instruction) != nullptr || + dynamic_cast(&instruction) != nullptr || + dynamic_cast(&instruction) != nullptr || + dynamic_cast(&instruction) != nullptr || + dynamic_cast(&instruction) != nullptr || + dynamic_cast(&instruction) != nullptr || + dynamic_cast(&instruction) != nullptr || + dynamic_cast(&instruction) != nullptr) { + changed = + substitute_lhs_if_constant(instruction, environment) || changed; + changed = + substitute_rhs_if_constant(instruction, environment) || changed; + return changed; + } + + if (dynamic_cast(&instruction) != nullptr || + dynamic_cast(&instruction) != nullptr || + dynamic_cast(&instruction) != nullptr || + dynamic_cast(&instruction) != nullptr || + dynamic_cast(&instruction) != nullptr || + dynamic_cast(&instruction) != nullptr || + dynamic_cast(&instruction) != nullptr) { + changed = + substitute_rhs_if_constant(instruction, environment) || changed; + return changed; + } + + if (dynamic_cast(&instruction) != nullptr || + dynamic_cast(&instruction) != nullptr) { + changed = + substitute_lhs_if_constant(instruction, environment) || changed; + return changed; + } + + return changed; +} + +[[nodiscard]] std::optional +defined_variable(const Tac &instruction) { + if (dynamic_cast(&instruction) != nullptr || + dynamic_cast(&instruction) != nullptr || + dynamic_cast(&instruction) != nullptr || + dynamic_cast(&instruction) != nullptr || + dynamic_cast(&instruction) != nullptr || + dynamic_cast(&instruction) != nullptr || + dynamic_cast(&instruction) != nullptr || + dynamic_cast(&instruction) != nullptr || + dynamic_cast(&instruction) != nullptr || + dynamic_cast(&instruction) != nullptr || + dynamic_cast(&instruction) != nullptr || + dynamic_cast(&instruction) != nullptr || + dynamic_cast(&instruction) != nullptr || + dynamic_cast(&instruction) != nullptr || + dynamic_cast(&instruction) != nullptr || + dynamic_cast(&instruction) != nullptr) { + return instruction.getResult(); + } + return std::nullopt; +} + +bool process_block(BBlock &block) { + ConstantEnvironment environment; + bool changed = false; + + auto &instructions = block.getInstructions(); + for (auto &instruction_ptr : instructions) { + auto &instruction = *instruction_ptr; + changed = + substitute_constants_in_instruction(instruction, environment) || + changed; + + const auto folded_value = + try_fold_instruction(instruction, environment); + if (folded_value.has_value()) { + const auto folded_immediate = to_ir_immediate(*folded_value); + if (folded_immediate.has_value()) { + const auto result = instruction.getResult(); + instruction_ptr = + std::make_unique(*folded_immediate, result); + environment[result] = *folded_value; + changed = true; + continue; + } + } + + if (dynamic_cast(&instruction) != nullptr) { + environment.clear(); + if (const auto result = defined_variable(instruction); + result.has_value()) { + environment.erase(*result); + } + continue; + } + + if (const auto *copy = dynamic_cast(&instruction); + copy != nullptr) { + const auto constant = + resolve_constant_operand(copy->getRhsOperand(), environment); + if (constant.has_value()) { + environment[instruction.getResult()] = *constant; + } else { + environment.erase(instruction.getResult()); + } + continue; + } + + if (const auto result = defined_variable(instruction); + result.has_value()) { + environment.erase(*result); + } + } + + return changed; +} + +bool process_method_root(BBlock *root) { + std::vector stack{root}; + std::unordered_set visited; + bool changed = false; + + while (!stack.empty()) { + auto *block = stack.back(); + stack.pop_back(); + if (!visited.insert(block).second) { + continue; + } + + changed = process_block(*block) || changed; + if (block->hasTrueBlock()) { + stack.push_back(block->getTrueBlock()); + } + if (block->hasFalseBlock()) { + stack.push_back(block->getFalseBlock()); + } + } + + return changed; +} + +} // namespace + +bool ConstantFoldingPass::run(CFG &graph) { + bool changed = false; + for (auto *root : graph.getMethodRoots()) { + if (root == nullptr) { + continue; + } + changed = process_method_root(root) || changed; + } + return changed; +} diff --git a/src/ir/passes/ConstantFoldingPass.hpp b/src/ir/passes/ConstantFoldingPass.hpp new file mode 100644 index 0000000..a806a97 --- /dev/null +++ b/src/ir/passes/ConstantFoldingPass.hpp @@ -0,0 +1,16 @@ +#ifndef CONSTANT_FOLDING_PASS_HPP +#define CONSTANT_FOLDING_PASS_HPP + +#include + +#include "ir/passes/IRPass.hpp" + +class ConstantFoldingPass final : public IRPass { + public: + [[nodiscard]] std::string_view name() const override { + return "constant-folding"; + } + bool run(CFG &graph) override; +}; + +#endif diff --git a/src/ir/passes/IRPass.hpp b/src/ir/passes/IRPass.hpp new file mode 100644 index 0000000..f1f0c5b --- /dev/null +++ b/src/ir/passes/IRPass.hpp @@ -0,0 +1,16 @@ +#ifndef IR_PASS_HPP +#define IR_PASS_HPP + +#include + +class CFG; + +class IRPass { + public: + virtual ~IRPass() = default; + + [[nodiscard]] virtual std::string_view name() const = 0; + virtual bool run(CFG &graph) = 0; +}; + +#endif diff --git a/src/ir/passes/IRPassManager.cpp b/src/ir/passes/IRPassManager.cpp new file mode 100644 index 0000000..56f6b23 --- /dev/null +++ b/src/ir/passes/IRPassManager.cpp @@ -0,0 +1,19 @@ +#include "ir/passes/IRPassManager.hpp" + +#include +#include + +#include "ir/CFG.hpp" +#include "ir/passes/IRPass.hpp" + +void IRPassManager::addPass(std::unique_ptr pass) { + passes_.push_back(std::move(pass)); +} + +bool IRPassManager::run(CFG &graph) const { + bool changed = false; + for (const auto &pass : passes_) { + changed = pass->run(graph) || changed; + } + return changed; +} diff --git a/src/ir/passes/IRPassManager.hpp b/src/ir/passes/IRPassManager.hpp new file mode 100644 index 0000000..6af09f6 --- /dev/null +++ b/src/ir/passes/IRPassManager.hpp @@ -0,0 +1,19 @@ +#ifndef IR_PASS_MANAGER_HPP +#define IR_PASS_MANAGER_HPP + +#include +#include + +class CFG; +class IRPass; + +class IRPassManager { + public: + void addPass(std::unique_ptr pass); + [[nodiscard]] bool run(CFG &graph) const; + + private: + std::vector> passes_; +}; + +#endif diff --git a/src/main.cpp b/src/main.cpp index e768d30..4ff8dc1 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -11,6 +11,8 @@ namespace fs = std::filesystem; #include "bytecode/BytecodeProgram.hpp" #include "ir/CFG.hpp" #include "ir/IRGenerationVisitor.hpp" +#include "ir/passes/ConstantFoldingPass.hpp" +#include "ir/passes/IRPassManager.hpp" #include "lexing/LegacyDiagnostics.hpp" #include "lexing/Lexer.hpp" #include "lexing/SourceBuffer.hpp" @@ -315,6 +317,11 @@ int main(int argc, char **argv) { std::cout << "IR generation failed.\n"; return errCodes::SEMANTIC_ERROR; } + + IRPassManager pass_manager; + pass_manager.addPass(std::make_unique()); + (void)pass_manager.run(graph); + graph.printGraphviz(controlFlowGraph); std::ofstream stGraph(outputDirectory / "st.dot"); diff --git a/src/util/serialize.cpp b/src/util/serialize.cpp index 17b4a53..a69609a 100644 --- a/src/util/serialize.cpp +++ b/src/util/serialize.cpp @@ -6,6 +6,9 @@ void Serializer::writeInteger(size_t value) { os.write(reinterpret_cast(&value), sizeof(value)); } +void Serializer::writeSignedInteger(std::int64_t value) { + os.write(reinterpret_cast(&value), sizeof(value)); +} void Serializer::writeOpcode(Opcode value) { os.write(reinterpret_cast(&value), sizeof(value)); } @@ -25,6 +28,11 @@ size_t Deserializer::readInteger() { is.read(reinterpret_cast(&value), sizeof(value)); return value; } +std::int64_t Deserializer::readSignedInteger() { + std::int64_t value = 0; + is.read(reinterpret_cast(&value), sizeof(value)); + return value; +} std::string Deserializer::readString() { const auto length = readInteger(); std::string str(length, '\0'); diff --git a/src/util/serialize.hpp b/src/util/serialize.hpp index f88d485..9ee2dbd 100644 --- a/src/util/serialize.hpp +++ b/src/util/serialize.hpp @@ -1,6 +1,7 @@ #ifndef SERIALIZE_HPP #define SERIALIZE_HPP +#include #include #include #include @@ -15,6 +16,7 @@ class Serializer { explicit Serializer(std::ofstream &stream) : os(std::move(stream)) {} void writeInteger(size_t value); + void writeSignedInteger(std::int64_t value); void writeOpcode(Opcode value); void writeString(const std::string &str); void writeStringVector(const std::vector &vec); @@ -28,6 +30,7 @@ class Deserializer { explicit Deserializer(std::ifstream &stream) : is(std::move(stream)) {} size_t readInteger(); + std::int64_t readSignedInteger(); Opcode readOpcode(); std::string readString(); std::vector readStringVector(); diff --git a/src/vm/vm.cpp b/src/vm/vm.cpp index cc8ce20..ec85058 100644 --- a/src/vm/vm.cpp +++ b/src/vm/vm.cpp @@ -16,9 +16,9 @@ struct Instruction { Opcode op; - size_t argNumber; + std::int64_t argNumber; std::string argString; - Instruction(Opcode op_, size_t argNum_, const std::string &argStr_) + Instruction(Opcode op_, std::int64_t argNum_, const std::string &argStr_) : op(op_), argNumber(argNum_), argString(argStr_) {}; }; @@ -90,8 +90,7 @@ class Activation { const auto &step() { const auto it = method.blocks.find(currentBlock); if (it == method.blocks.end()) { - throw std::invalid_argument("block " + currentBlock + - " not found"); + throw std::invalid_argument("block " + currentBlock + " not found"); } const auto &block = it->second; if (pc >= block.instructions.size()) { @@ -460,7 +459,7 @@ void VM::run() { break; } case Opcode::CONST: { - push(static_cast(instruction.argNumber)); + push(instruction.argNumber); break; } case Opcode::LOAD: { @@ -480,7 +479,7 @@ void VM::run() { } [[nodiscard]] Instruction readInstruction(Deserializer &reader) { - size_t argNumber = 0; + std::int64_t argNumber = 0; std::string argString; auto op = reader.readOpcode(); switch (op) { @@ -494,7 +493,7 @@ void VM::run() { break; } case Opcode::CONST: { - argNumber = reader.readInteger(); + argNumber = reader.readSignedInteger(); break; } default: { diff --git a/tests/ir_constant_folding_test.cpp b/tests/ir_constant_folding_test.cpp new file mode 100644 index 0000000..3534a18 --- /dev/null +++ b/tests/ir_constant_folding_test.cpp @@ -0,0 +1,263 @@ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ast/Node.h" +#include "bytecode/BytecodeInstruction.hpp" +#include "bytecode/BytecodeProgram.hpp" +#include "bytecode/Opcode.hpp" +#include "ir/CFG.hpp" +#include "ir/IRGenerationVisitor.hpp" +#include "ir/passes/ConstantFoldingPass.hpp" +#include "ir/passes/IRPassManager.hpp" +#include "lexing/Diagnostics.hpp" +#include "lexing/Lexer.hpp" +#include "lexing/StringViewStream.hpp" +#include "parsing/Parser.hpp" +#include "semantic/SymbolTable.hpp" +#include "semantic/SymbolTableVisitor.hpp" +#include "semantic/TypeCheckVisitor.hpp" +#include "util/serialize.hpp" + +namespace { + +class CollectingDiagnosticSink final : public lexing::DiagnosticSink { + public: + void emit(lexing::Diagnostic d) override { diagnostics.push_back(d); } + + [[nodiscard]] int error_count() const { + int count = 0; + for (const auto &d : diagnostics) { + if (d.severity == lexing::Severity::Error) { + count += 1; + } + } + return count; + } + + std::vector diagnostics; +}; + +std::unique_ptr +compile_with_constant_folding(std::string_view source) { + CollectingDiagnosticSink parser_diag; + auto stream = std::make_unique(source); + lexing::Lexer lexer(std::move(stream), source, &parser_diag); + parsing::Parser parser(std::move(lexer), &parser_diag); + auto parse_result = parser.parse_goal(); + + EXPECT_TRUE(parse_result.has_value()); + EXPECT_EQ(parser_diag.error_count(), 0); + if (!parse_result.has_value() || parser_diag.error_count() != 0) { + return nullptr; + } + + auto root = std::move(parse_result.value()); + + SymbolTable symbol_table; + CollectingDiagnosticSink semantic_diag; + const auto symbol_table_result = + build_symbol_table(*root, symbol_table, &semantic_diag); + EXPECT_TRUE(symbol_table_result.ok()); + + TypeInfo type_info; + const auto type_check_result = + check_types(*root, symbol_table, &type_info, &semantic_diag); + EXPECT_TRUE(type_check_result.ok()); + EXPECT_EQ(semantic_diag.error_count(), 0); + if (!symbol_table_result.ok() || !type_check_result.ok() || + semantic_diag.error_count() != 0) { + return nullptr; + } + + CFG graph; + graph.setTypeInfo(&type_info); + const auto ir_result = + generate_ir(*root, graph, symbol_table, &semantic_diag); + EXPECT_TRUE(ir_result.ok()); + EXPECT_EQ(semantic_diag.error_count(), 0); + if (!ir_result.ok() || semantic_diag.error_count() != 0) { + return nullptr; + } + + IRPassManager pass_manager; + pass_manager.addPass(std::make_unique()); + (void)pass_manager.run(graph); + + auto program = std::make_unique(); + graph.generateBytecode(*program, symbol_table); + return program; +} + +[[nodiscard]] std::vector +collect_instructions(const BytecodeProgram &program) { + return program.getInstructions(); +} + +[[nodiscard]] bool contains_instruction( + const std::vector &instructions, + Opcode opcode) { + for (const auto *instruction : instructions) { + if (instruction->getOpcode() == opcode) { + return true; + } + } + return false; +} + +[[nodiscard]] bool +contains_const(const std::vector &instructions, + std::int64_t value) { + for (const auto *instruction : instructions) { + const auto *constant = + dynamic_cast(instruction); + if (constant == nullptr) { + continue; + } + if (constant->getOpcode() == Opcode::CONST && + constant->getParam() == value) { + return true; + } + } + return false; +} + +} // namespace + +TEST(IRConstantFolding, ArithmeticFoldRemovesAddAndMultiply) { + constexpr std::string_view source = + R"(public class Main { + public static void main(String[] args) { + System.out.println(new Foo().run()); + } +} + +class Foo { + public int run() { + int x; + x = 2 + 3 * 4; + return x; + } +} +)"; + + const auto program = compile_with_constant_folding(source); + ASSERT_NE(program, nullptr); + const auto instructions = collect_instructions(*program); + EXPECT_TRUE(contains_const(instructions, 14)); + EXPECT_FALSE(contains_instruction(instructions, Opcode::ADD)); + EXPECT_FALSE(contains_instruction(instructions, Opcode::MUL)); +} + +TEST(IRConstantFolding, ComparisonAndBooleanExpressionFoldsToConstant) { + constexpr std::string_view source = + R"(public class Main { + public static void main(String[] args) { + if (new Foo().run()) { + System.out.println(1); + } else { + System.out.println(0); + } + } +} + +class Foo { + public boolean run() { + boolean x; + x = (1 < 2) && (3 == 3); + return x; + } +} +)"; + + const auto program = compile_with_constant_folding(source); + ASSERT_NE(program, nullptr); + const auto instructions = collect_instructions(*program); + EXPECT_TRUE(contains_const(instructions, 1)); + EXPECT_FALSE(contains_instruction(instructions, Opcode::LT)); + EXPECT_FALSE(contains_instruction(instructions, Opcode::EQ)); + EXPECT_FALSE(contains_instruction(instructions, Opcode::AND)); +} + +TEST(IRConstantFolding, NegativeConstantFoldProducesSignedIconst) { + constexpr std::string_view source = + R"(public class Main { + public static void main(String[] args) { + System.out.println(new Foo().run()); + } +} + +class Foo { + public int run() { + int x; + x = 0 - 1; + return x; + } +} +)"; + + const auto program = compile_with_constant_folding(source); + ASSERT_NE(program, nullptr); + const auto instructions = collect_instructions(*program); + EXPECT_TRUE(contains_const(instructions, -1)); +} + +TEST(IRConstantFolding, DivisionByZeroIsNotFolded) { + constexpr std::string_view source = + R"(public class Main { + public static void main(String[] args) { + System.out.println(new Foo().run()); + } +} + +class Foo { + public int run() { + int x; + x = 4 / 0; + return x; + } +} +)"; + + const auto program = compile_with_constant_folding(source); + ASSERT_NE(program, nullptr); + const auto instructions = collect_instructions(*program); + EXPECT_TRUE(contains_instruction(instructions, Opcode::DIV)); +} + +TEST(IRConstantFolding, SignedSerializerRoundTrip) { + const auto unique_suffix = std::to_string( + std::chrono::steady_clock::now().time_since_epoch().count()); + const auto path = std::filesystem::temp_directory_path() / + ("minijava_signed_serializer_" + unique_suffix + ".bc"); + + { + std::ofstream stream(path, std::ios::binary); + ASSERT_TRUE(stream.is_open()); + Serializer serializer(stream); + serializer.writeSignedInteger(-1); + serializer.writeSignedInteger(0); + serializer.writeSignedInteger(42); + } + + { + std::ifstream stream(path, std::ios::binary); + ASSERT_TRUE(stream.is_open()); + Deserializer deserializer(stream); + EXPECT_EQ(deserializer.readSignedInteger(), -1); + EXPECT_EQ(deserializer.readSignedInteger(), 0); + EXPECT_EQ(deserializer.readSignedInteger(), 42); + } + + std::error_code error; + std::filesystem::remove(path, error); + EXPECT_FALSE(error); +}