diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index d5435237ec7b..fcd8cf90a03b 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -158,6 +158,8 @@ set(libyul_sources libyul/SyntaxTest.cpp libyul/YulInterpreterTest.cpp libyul/YulInterpreterTest.h + libyul/YulOptimizerAssemblyTest.cpp + libyul/YulOptimizerAssemblyTest.h libyul/YulOptimizerTest.cpp libyul/YulOptimizerTest.h libyul/YulOptimizerTestCommon.cpp diff --git a/test/InteractiveTests.h b/test/InteractiveTests.h index 42e20d7e3919..b8e05f9da9bb 100644 --- a/test/InteractiveTests.h +++ b/test/InteractiveTests.h @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -61,27 +62,28 @@ struct Testsuite /// Array of testsuits that can be run interactively as well as automatically Testsuite const g_interactiveTestsuites[] = { /* - Title Path Subpath SMT NeedsVM Creator function */ - {"Yul Optimizer", "libyul", "yulOptimizerTests", false, false, &yul::test::YulOptimizerTest::create}, - {"Yul Interpreter", "libyul", "yulInterpreterTests", false, false, &yul::test::YulInterpreterTest::create}, - {"Yul Object Compiler", "libyul", "objectCompiler", false, false, &yul::test::ObjectCompilerTest::create}, - {"Yul Control Flow Graph", "libyul", "yulControlFlowGraph", false, false, &yul::test::ControlFlowGraphTest::create}, - {"Yul Stack Layout", "libyul", "yulStackLayout", false, false, &yul::test::StackLayoutGeneratorTest::create}, - {"Yul Stack Shuffling", "libyul", "yulStackShuffling", false, false, &yul::test::StackShufflingTest::create}, - {"Control Flow Side Effects", "libyul", "controlFlowSideEffects", false, false, &yul::test::ControlFlowSideEffectsTest::create}, - {"Function Side Effects", "libyul", "functionSideEffects", false, false, &yul::test::FunctionSideEffects::create}, - {"Yul Syntax", "libyul", "yulSyntaxTests", false, false, &yul::test::SyntaxTest::create}, - {"EVM Code Transform", "libyul", "evmCodeTransform", false, false, &yul::test::EVMCodeTransformTest::create, {"nooptions"}}, - {"Syntax", "libsolidity", "syntaxTests", false, false, &SyntaxTest::create}, - {"Semantic", "libsolidity", "semanticTests", false, true, &SemanticTest::create}, - {"JSON AST", "libsolidity", "ASTJSON", false, false, &ASTJSONTest::create}, - {"JSON ABI", "libsolidity", "ABIJson", false, false, &ABIJsonTest::create}, - {"JSON Natspec", "libsolidity", "natspecJSON", false, false, &NatspecJSONTest::create}, - {"SMT Checker", "libsolidity", "smtCheckerTests", true, false, &SMTCheckerTest::create}, - {"Gas Estimates", "libsolidity", "gasTests", false, false, &GasTest::create}, - {"Memory Guard", "libsolidity", "memoryGuardTests", false, false, &MemoryGuardTest::create}, - {"AST Properties", "libsolidity", "astPropertyTests", false, false, &ASTPropertyTest::create}, - {"Function Dependency Graph", "libsolidity", "functionDependencyGraphTests", false, false, &FunctionDependencyGraphTest::create}, + Title Path Subpath SMT NeedsVM Creator function */ + {"Yul Optimizer", "libyul", "yulOptimizerTests", false, false, &yul::test::YulOptimizerTest::create}, + {"Yul Optimizer with Assembly", "libyul", "yulOptimizerAssemblyTests", false, false, &yul::test::YulOptimizerAssemblyTest::create}, + {"Yul Interpreter", "libyul", "yulInterpreterTests", false, false, &yul::test::YulInterpreterTest::create}, + {"Yul Object Compiler", "libyul", "objectCompiler", false, false, &yul::test::ObjectCompilerTest::create}, + {"Yul Control Flow Graph", "libyul", "yulControlFlowGraph", false, false, &yul::test::ControlFlowGraphTest::create}, + {"Yul Stack Layout", "libyul", "yulStackLayout", false, false, &yul::test::StackLayoutGeneratorTest::create}, + {"Yul Stack Shuffling", "libyul", "yulStackShuffling", false, false, &yul::test::StackShufflingTest::create}, + {"Control Flow Side Effects", "libyul", "controlFlowSideEffects", false, false, &yul::test::ControlFlowSideEffectsTest::create}, + {"Function Side Effects", "libyul", "functionSideEffects", false, false, &yul::test::FunctionSideEffects::create}, + {"Yul Syntax", "libyul", "yulSyntaxTests", false, false, &yul::test::SyntaxTest::create}, + {"EVM Code Transform", "libyul", "evmCodeTransform", false, false, &yul::test::EVMCodeTransformTest::create, {"nooptions"}}, + {"Syntax", "libsolidity", "syntaxTests", false, false, &SyntaxTest::create}, + {"Semantic", "libsolidity", "semanticTests", false, true, &SemanticTest::create}, + {"JSON AST", "libsolidity", "ASTJSON", false, false, &ASTJSONTest::create}, + {"JSON ABI", "libsolidity", "ABIJson", false, false, &ABIJsonTest::create}, + {"JSON Natspec", "libsolidity", "natspecJSON", false, false, &NatspecJSONTest::create}, + {"SMT Checker", "libsolidity", "smtCheckerTests", true, false, &SMTCheckerTest::create}, + {"Gas Estimates", "libsolidity", "gasTests", false, false, &GasTest::create}, + {"Memory Guard", "libsolidity", "memoryGuardTests", false, false, &MemoryGuardTest::create}, + {"AST Properties", "libsolidity", "astPropertyTests", false, false, &ASTPropertyTest::create}, + {"Function Dependency Graph", "libsolidity", "functionDependencyGraphTests", false, false, &FunctionDependencyGraphTest::create}, }; } diff --git a/test/libyul/YulOptimizerAssemblyTest.cpp b/test/libyul/YulOptimizerAssemblyTest.cpp new file mode 100644 index 000000000000..56cc8418321c --- /dev/null +++ b/test/libyul/YulOptimizerAssemblyTest.cpp @@ -0,0 +1,155 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +// SPDX-License-Identifier: GPL-3.0 + +#include +#include + +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include + +#include + +using namespace solidity; +using namespace solidity::util; +using namespace solidity::langutil; +using namespace solidity::yul; +using namespace solidity::yul::test; +using namespace solidity::frontend; +using namespace solidity::frontend::test; + +YulOptimizerAssemblyTest::YulOptimizerAssemblyTest(std::string const& _filename): + EVMVersionRestrictedTestCase(_filename) +{ + boost::filesystem::path path(_filename); + + if (path.empty() || std::next(path.begin()) == path.end() || std::next(std::next(path.begin())) == path.end()) + BOOST_THROW_EXCEPTION(std::runtime_error("Filename path has to contain a directory: \"" + _filename + "\".")); + m_optimizerStep = std::prev(std::prev(path.end()))->string(); + + m_source = m_reader.source(); + + auto dialectName = m_reader.stringSetting("dialect", "evm"); + m_dialect = &dialect(dialectName, solidity::test::CommonOptions::get().evmVersion()); + + m_expectation = m_reader.simpleExpectations(); +} + +TestCase::TestResult YulOptimizerAssemblyTest::run(std::ostream& _stream, std::string const& _linePrefix, bool const _formatted) +{ + std::tie(m_object, m_analysisInfo) = parse(_stream, _linePrefix, _formatted, m_source); + if (!m_object) + return TestResult::FatalError; + + soltestAssert(m_dialect, "Dialect not set."); + + m_object->analysisInfo = m_analysisInfo; + YulOptimizerTestCommon tester(m_object, *m_dialect); + tester.setStep(m_optimizerStep); + + if (!tester.runStep()) + { + AnsiColorized(_stream, _formatted, {formatting::BOLD, formatting::RED}) << _linePrefix << "Invalid optimizer step: " << m_optimizerStep << std::endl; + return TestResult::FatalError; + } + + auto const printed = (m_object->subObjects.empty() ? AsmPrinter{ *m_dialect }(*m_object->code) : m_object->toString(m_dialect)); + + // Re-parse new code for compilability + // TODO: support for wordSizeTransform which needs different input and output dialects + if (m_optimizerStep != "wordSizeTransform" && !std::get<0>(parse(_stream, _linePrefix, _formatted, printed))) + { + util::AnsiColorized(_stream, _formatted, {util::formatting::BOLD, util::formatting::CYAN}) + << _linePrefix << "Result after the optimiser:" << std::endl; + printPrefixed(_stream, printed, _linePrefix + " "); + return TestResult::FatalError; + } + + m_obtainedResult = "step: " + m_optimizerStep + "\n\n" + printed + "\n"; + + OptimiserSettings settings = OptimiserSettings::none(); + YulStack stack( + EVMVersion{}, + std::nullopt, + YulStack::Language::StrictAssembly, + settings, + DebugInfoSelection::All() + ); + if (!stack.parseAndAnalyze("", printed)) + { + AnsiColorized(_stream, _formatted, {formatting::BOLD, formatting::RED}) << _linePrefix << "Error parsing source." << std::endl; + SourceReferenceFormatter{_stream, stack, true, false} + .printErrorInformation(stack.errors()); + return TestResult::FatalError; + } + + evmasm::Assembly assembly{solidity::test::CommonOptions::get().evmVersion(), false, {}}; + EthAssemblyAdapter adapter(assembly); + EVMObjectCompiler::compile( + *stack.parserResult(), + adapter, + EVMDialect::strictAssemblyForEVMObjects(EVMVersion{}), + settings.optimizeStackAllocation, + std::nullopt + ); + + std::ostringstream output; + output << assembly; + m_obtainedResult += "\nAssembly:\n" + output.str(); + + return checkResult(_stream, _linePrefix, _formatted); +} + +std::pair, std::shared_ptr> YulOptimizerAssemblyTest::parse( + std::ostream& _stream, + std::string const& _linePrefix, + bool const _formatted, + std::string const& _source +) +{ + ErrorList errors; + soltestAssert(m_dialect, ""); + std::shared_ptr object; + std::shared_ptr analysisInfo; + std::tie(object, analysisInfo) = yul::test::parse(_source, *m_dialect, errors); + if (!object || !analysisInfo || Error::containsErrors(errors)) + { + AnsiColorized(_stream, _formatted, {formatting::BOLD, formatting::RED}) << _linePrefix << "Error parsing source." << std::endl; + CharStream charStream(_source, ""); + SourceReferenceFormatter{_stream, SingletonCharStreamProvider(charStream), true, false} + .printErrorInformation(errors); + return {}; + } + return {std::move(object), std::move(analysisInfo)}; +} diff --git a/test/libyul/YulOptimizerAssemblyTest.h b/test/libyul/YulOptimizerAssemblyTest.h new file mode 100644 index 000000000000..35fd09569db5 --- /dev/null +++ b/test/libyul/YulOptimizerAssemblyTest.h @@ -0,0 +1,63 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +// SPDX-License-Identifier: GPL-3.0 + +#pragma once + +#include + +namespace solidity::langutil +{ +class Error; +using ErrorList = std::vector>; +} + +namespace solidity::yul +{ +struct AsmAnalysisInfo; +struct Object; +struct Dialect; +} + +namespace solidity::yul::test +{ + +class YulOptimizerAssemblyTest: public solidity::frontend::test::EVMVersionRestrictedTestCase +{ +public: + static std::unique_ptr create(Config const& _config) + { + return std::make_unique(_config.filename); + } + + explicit YulOptimizerAssemblyTest(std::string const& _filename); + + TestResult run(std::ostream& _stream, std::string const& _linePrefix = "", bool const _formatted = false) override; +private: + std::pair, std::shared_ptr> parse( + std::ostream& _stream, std::string const& _linePrefix, bool const _formatted, std::string const& _source + ); + + std::string m_optimizerStep; + + Dialect const* m_dialect = nullptr; + + std::shared_ptr m_object; + std::shared_ptr m_analysisInfo; +}; + +} diff --git a/test/libyul/yulOptimizerAssemblyTests/blockFlattener/basic.yul b/test/libyul/yulOptimizerAssemblyTests/blockFlattener/basic.yul new file mode 100644 index 000000000000..494a9f3dd71b --- /dev/null +++ b/test/libyul/yulOptimizerAssemblyTests/blockFlattener/basic.yul @@ -0,0 +1,62 @@ +{ + { + let _1 := mload(0) + let f_a := mload(1) + let f_r + { + f_a := mload(f_a) + f_r := add(f_a, calldatasize()) + } + let z := mload(2) + } +} +// ---- +// step: blockFlattener +// +// { +// { +// let _1 := mload(0) +// let f_a := mload(1) +// let f_r +// f_a := mload(f_a) +// f_r := add(f_a, calldatasize()) +// let z := mload(2) +// } +// } +// +// Assembly: +// /* "":32:33 */ +// 0x00 +// /* "":26:34 */ +// mload +// /* "":60:61 */ +// 0x01 +// /* "":54:62 */ +// mload +// /* "":71:78 */ +// 0x00 +// /* "":100:103 */ +// dup2 +// /* "":94:104 */ +// mload +// /* "":87:104 */ +// swap2 +// pop +// /* "":129:143 */ +// calldatasize +// /* "":124:127 */ +// dup3 +// /* "":120:144 */ +// add +// /* "":113:144 */ +// swap1 +// pop +// /* "":168:169 */ +// 0x02 +// /* "":162:170 */ +// mload +// /* "":6:176 */ +// pop +// pop +// pop +// pop diff --git a/test/tools/CMakeLists.txt b/test/tools/CMakeLists.txt index 532a08d9fc55..0512b474c76c 100644 --- a/test/tools/CMakeLists.txt +++ b/test/tools/CMakeLists.txt @@ -46,6 +46,7 @@ add_executable(isoltest ../libyul/StackShufflingTest.cpp ../libyul/StackLayoutGeneratorTest.cpp ../libyul/YulOptimizerTest.cpp + ../libyul/YulOptimizerAssemblyTest.cpp ../libyul/YulOptimizerTestCommon.cpp ../libyul/YulInterpreterTest.cpp )