From d01bc4039d0632d92283014cbc2b8dc70ff8c574 Mon Sep 17 00:00:00 2001 From: Daniel Kirchner Date: Fri, 10 May 2019 03:10:48 +0200 Subject: [PATCH] Preliminary Yul block outliner implementation. --- libyul/CMakeLists.txt | 4 + libyul/YulString.h | 2 + libyul/optimiser/ASTCopier.h | 2 +- libyul/optimiser/BlockClassFinder.cpp | 248 ++++++++++++++++++ libyul/optimiser/BlockClassFinder.h | 118 +++++++++ libyul/optimiser/BlockOutliner.cpp | 175 ++++++++++++ libyul/optimiser/BlockOutliner.h | 62 +++++ libyul/optimiser/SyntacticalEquality.cpp | 14 + libyul/optimiser/SyntacticalEquality.h | 3 + test/libyul/YulOptimizerTest.cpp | 8 + .../yulOptimizerTests/blockOutliner/basic.yul | 91 +++++++ .../blockOutliner/break_continue.yul | 75 ++++++ .../blockOutliner/nested.yul | 20 ++ .../blockOutliner/nested_inner.yul | 22 ++ .../blockOutliner/only_assign.yul | 27 ++ 15 files changed, 870 insertions(+), 1 deletion(-) create mode 100644 libyul/optimiser/BlockClassFinder.cpp create mode 100644 libyul/optimiser/BlockClassFinder.h create mode 100644 libyul/optimiser/BlockOutliner.cpp create mode 100644 libyul/optimiser/BlockOutliner.h create mode 100644 test/libyul/yulOptimizerTests/blockOutliner/basic.yul create mode 100644 test/libyul/yulOptimizerTests/blockOutliner/break_continue.yul create mode 100644 test/libyul/yulOptimizerTests/blockOutliner/nested.yul create mode 100644 test/libyul/yulOptimizerTests/blockOutliner/nested_inner.yul create mode 100644 test/libyul/yulOptimizerTests/blockOutliner/only_assign.yul diff --git a/libyul/CMakeLists.txt b/libyul/CMakeLists.txt index ff4d2cdbb..3d6beefee 100644 --- a/libyul/CMakeLists.txt +++ b/libyul/CMakeLists.txt @@ -54,6 +54,10 @@ add_library(yul optimiser/ASTWalker.h optimiser/BlockFlattener.cpp optimiser/BlockFlattener.h + optimiser/BlockClassFinder.cpp + optimiser/BlockClassFinder.h + optimiser/BlockOutliner.cpp + optimiser/BlockOutliner.h optimiser/CommonSubexpressionEliminator.cpp optimiser/CommonSubexpressionEliminator.h optimiser/ControlFlowSimplifier.cpp diff --git a/libyul/YulString.h b/libyul/YulString.h index 5e77e0bbd..c265a994c 100644 --- a/libyul/YulString.h +++ b/libyul/YulString.h @@ -122,6 +122,8 @@ public: return YulStringRepository::instance().idToString(m_handle.id); } + std::uint64_t hash() const { return m_handle.hash; } + private: /// Handle of the string. Assumes that the empty string has ID zero. YulStringRepository::Handle m_handle{ 0, YulStringRepository::emptyHash() }; diff --git a/libyul/optimiser/ASTCopier.h b/libyul/optimiser/ASTCopier.h index b2e0a383a..af5cc6a6d 100644 --- a/libyul/optimiser/ASTCopier.h +++ b/libyul/optimiser/ASTCopier.h @@ -102,7 +102,7 @@ protected: return _v ? std::make_unique(translate(*_v)) : nullptr; } - Block translate(Block const& _block); + virtual Block translate(Block const& _block); Case translate(Case const& _case); virtual Identifier translate(Identifier const& _identifier); Literal translate(Literal const& _literal); diff --git a/libyul/optimiser/BlockClassFinder.cpp b/libyul/optimiser/BlockClassFinder.cpp new file mode 100644 index 000000000..756194a87 --- /dev/null +++ b/libyul/optimiser/BlockClassFinder.cpp @@ -0,0 +1,248 @@ +/* + 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 . +*/ +/** + * Optimiser component that finds classes of equivalent blocks. + */ + +#include +#include +#include + +using namespace std; +using namespace dev; +using namespace yul; + +namespace { +static constexpr uint64_t compileTimeLiteralHash(char const* _literal, size_t _N) +{ + return (_N == 0) ? 14695981039346656037u : compileTimeLiteralHash(_literal + 1, _N - 1) ^ 1099511628211u; +} + +template +static constexpr uint64_t compileTimeLiteralHash(char const (&_literal)[N]) +{ + return compileTimeLiteralHash(_literal, N); +} +} + +bool BlockClassFinder::isExternal(YulString _identifier) const +{ + auto it = m_identifierMapping.find(_identifier); + yulAssert(it != m_identifierMapping.end(), ""); + return (it->second & 1) == 0; +} + +std::vector BlockClassFinder::run(Block const& _block) +{ + GlobalState result; + BlockClassFinder blockClassFinder(result); + blockClassFinder(_block); + return std::move(result.blockClasses); +} + +void BlockClassFinder::operator()(Literal const& _literal) +{ + hash(compileTimeLiteralHash("Literal")); + hash(_literal.value.hash()); + hash(_literal.type.hash()); + hash(static_cast>(_literal.kind)); +} + +void BlockClassFinder::operator()(Identifier const& _identifier) +{ + hash(compileTimeLiteralHash("Identifier")); + size_t id = 0; + auto it = m_identifierMapping.find(_identifier.name); + if (it == m_identifierMapping.end()) + { + id = 2 * (m_externalIdentifierCount++); + m_identifierMapping[_identifier.name] = id; + m_externalIdentifiers.emplace_back(_identifier.name); + } + else + id = it->second; + if ((id & 1) == 0) + { + if (m_isAssignmentLHS) + m_externalAssignments.insert(_identifier.name); + else + m_externalReads.insert(_identifier.name); + } + hash(id); +} + +void BlockClassFinder::operator()(FunctionalInstruction const& _instr) +{ + hash(compileTimeLiteralHash("FunctionalInstruction")); + hash(static_cast>(_instr.instruction)); + // Note that ASTWalker reverses the arguments. + walkVector(_instr.arguments); +} + +void BlockClassFinder::operator()(FunctionCall const& _funCall) +{ + hash(compileTimeLiteralHash("FunctionCall")); + hash(_funCall.arguments.size()); + hash(_funCall.functionName.name.hash()); + // Note that ASTWalker reverses the arguments. + walkVector(_funCall.arguments); +} + +void BlockClassFinder::operator()(ExpressionStatement const& _statement) +{ + hash(compileTimeLiteralHash("ExpressionStatement")); + ASTWalker::operator()(_statement); +} + +void BlockClassFinder::operator()(Assignment const& _assignment) +{ + hash(compileTimeLiteralHash("Assignment")); + hash(_assignment.variableNames.size()); + m_isAssignmentLHS = true; + for (auto const& name: _assignment.variableNames) + (*this)(name); + m_isAssignmentLHS = false; + visit(*_assignment.value); +} + +void BlockClassFinder::operator()(VariableDeclaration const& _varDecl) +{ + hash(compileTimeLiteralHash("VariableDeclaration")); + hash(_varDecl.variables.size()); + for (auto const& var: _varDecl.variables) + { + yulAssert(!m_identifierMapping.count(var.name), ""); + m_identifierMapping[var.name] = 2 * m_internalIdentifierCount + 1; + } + ASTWalker::operator()(_varDecl); +} + +void BlockClassFinder::operator()(If const& _if) +{ + hash(compileTimeLiteralHash("If")); + ASTWalker::operator()(_if); +} + +void BlockClassFinder::operator()(Switch const& _switch) +{ + hash(compileTimeLiteralHash("Switch")); + hash(_switch.cases.size()); + ASTWalker::operator()(_switch); +} + +void BlockClassFinder::operator()(FunctionDefinition const& _funDef) +{ + hash(compileTimeLiteralHash("FunctionDefinition")); + m_functionName = _funDef.name; + ASTWalker::operator()(_funDef); +} + +void BlockClassFinder::operator()(ForLoop const& _loop) +{ + hash(compileTimeLiteralHash("ForLoop")); + ++m_loopDepth; + ASTWalker::operator()(_loop); + --m_loopDepth; +} + +void BlockClassFinder::operator()(Break const& _break) +{ + hash(compileTimeLiteralHash("Break")); + if (!m_loopDepth) + m_hasFreeBreakOrContinue = true; + ASTWalker::operator()(_break); +} + +void BlockClassFinder::operator()(Continue const& _continue) +{ + hash(compileTimeLiteralHash("Continue")); + if (!m_loopDepth) + m_hasFreeBreakOrContinue = true; + ASTWalker::operator()(_continue); +} + + +void BlockClassFinder::operator()(Block const& _block) +{ + hash(compileTimeLiteralHash("Block")); + hash(_block.statements.size()); + if (_block.statements.empty()) + return; + + BlockClassFinder subBlockClassFinder(m_globalState); + for (auto const& statement: _block.statements) + subBlockClassFinder.visit(statement); + + // propagate sub block contents + hash(subBlockClassFinder.m_hash); + for (auto const& externalIdentifier: subBlockClassFinder.m_externalIdentifiers) + (*this)(Identifier{{}, externalIdentifier}); + for (auto const& externalAssignment: subBlockClassFinder.m_externalAssignments) + if (isExternal(externalAssignment)) + m_externalAssignments.insert(externalAssignment); + for (auto const& externalAssignment: subBlockClassFinder.m_externalReads) + if (isExternal(externalAssignment)) + m_externalReads.insert(externalAssignment); + if (!m_loopDepth && subBlockClassFinder.m_hasFreeBreakOrContinue) + m_hasFreeBreakOrContinue = true; + + // look for existing block class + auto& candidateIDs = m_globalState.hashToBlockIDs[subBlockClassFinder.m_hash]; + for (auto const& candidateID: candidateIDs) + { + auto const& candidate = m_globalState.block(candidateID); + if (subBlockClassFinder.m_externalIdentifiers.size() == candidate.externalReferences.size()) + { + if ( + SyntacticallyEqual{ + subBlockClassFinder.m_externalIdentifiers, + candidate.externalReferences + }.statementEqual(_block, *candidate.block) + ) + { + m_globalState.blockToClassID[&_block] = candidateID.blockClass; + m_globalState.blockClasses[candidateID.blockClass].members.emplace_back(BlockClassMember{ + &_block, + std::move(subBlockClassFinder.m_externalIdentifiers), + std::move(subBlockClassFinder.m_externalAssignments), + std::move(subBlockClassFinder.m_externalReads) + }); + if (!m_functionName.empty()) + { + m_globalState.blockClasses[candidateID.blockClass].nameHint = m_functionName; + m_functionName = {}; + } + return; + } + } + } + + // create new block class + candidateIDs.emplace_back(GlobalState::BlockID{m_globalState.blockClasses.size(), 0}); + m_globalState.blockToClassID[&_block] = m_globalState.blockClasses.size(); + m_globalState.blockClasses.emplace_back(BlockClass{ + make_vector(std::forward_as_tuple( + &_block, + std::move(subBlockClassFinder.m_externalIdentifiers), + std::move(subBlockClassFinder.m_externalAssignments), + std::move(subBlockClassFinder.m_externalReads) + )), + m_functionName, + subBlockClassFinder.m_hasFreeBreakOrContinue + }); + m_functionName = {}; +} diff --git a/libyul/optimiser/BlockClassFinder.h b/libyul/optimiser/BlockClassFinder.h new file mode 100644 index 000000000..685f69fcf --- /dev/null +++ b/libyul/optimiser/BlockClassFinder.h @@ -0,0 +1,118 @@ +/* + 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 . +*/ +/** + * Optimiser component that finds classes of equivalent blocks. + */ +#pragma once + +#include +#include +#include +#include + +namespace yul +{ + +struct BlockClassMember +{ + Block const* block = nullptr; + std::vector externalReferences; + std::set externalAssignments; + std::set externalReads; +}; + +struct BlockClass +{ + std::vector members; + YulString nameHint; + bool hasFreeBreakOrContinue = false; +}; + +/** + * Optimiser component that finds classes of equivalent blocks. + * + * Prerequisite: Disambiguator + * + * Works best after running the FunctionHoister and FunctionGrouper + */ +class BlockClassFinder: public ASTWalker +{ +public: + + using ASTWalker::operator(); + + void operator()(Literal const&) override; + void operator()(Identifier const&) override; + void operator()(FunctionalInstruction const& _instr) override; + void operator()(FunctionCall const& _funCall) override; + void operator()(ExpressionStatement const& _statement) override; + void operator()(Assignment const& _assignment) override; + void operator()(VariableDeclaration const& _varDecl) override; + void operator()(If const& _if) override; + void operator()(Switch const& _switch) override; + void operator()(FunctionDefinition const&) override; + void operator()(ForLoop const&) override; + void operator()(Break const&) override; + void operator()(Continue const&) override; + void operator()(Block const& _block) override; + + static std::vector run(Block const& _block); + +private: + struct GlobalState + { + struct BlockID + { + size_t blockClass = 0; + size_t indexInClass = 0; + }; + std::map> hashToBlockIDs; + std::map blockToClassID; + std::vector blockClasses; + BlockClassMember const& block(BlockID const& id) + { + return blockClasses.at(id.blockClass).members.at(id.indexInClass); + } + }; + + BlockClassFinder(GlobalState& _globalState): m_globalState(_globalState) {} + + void hash(uint64_t _value) + { + m_hash *= 1099511628211u; + m_hash ^= _value; + } + + GlobalState& m_globalState; + + bool isExternal(YulString _identifier) const; + + uint64_t m_hash = 14695981039346656037u; + std::map m_identifierMapping; + std::vector m_externalIdentifiers; + std::set m_externalAssignments; + std::set m_externalReads; + size_t m_externalIdentifierCount = 0; + size_t m_internalIdentifierCount = 0; + bool m_isAssignmentLHS = false; + size_t m_loopDepth = 0; + bool m_hasFreeBreakOrContinue = false; + YulString m_functionName; +}; + + +} diff --git a/libyul/optimiser/BlockOutliner.cpp b/libyul/optimiser/BlockOutliner.cpp new file mode 100644 index 000000000..ea0aa8841 --- /dev/null +++ b/libyul/optimiser/BlockOutliner.cpp @@ -0,0 +1,175 @@ +/* + 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 . +*/ +/** + * Optimiser component that outlines blocks that occur multiple times. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; +using namespace dev; +using namespace yul; + +bool BlockOutliner::shallOutline(BlockClass const& _blockClass) +{ + if (_blockClass.hasFreeBreakOrContinue) + return false; + if (_blockClass.members.size() < 2) + return false; + // outline everything for now for testing + // TODO: find good heuristics + return true; + /* + auto codeSize = CodeSize::codeSize(*_blockClass.members.front().block); + auto const& representative = _blockClass.members.front(); + if (representative.externalAssignments.size() > 5) + return false; + if (representative.externalReads.size() > 5) + return false; + if (representative.externalAssignments.size() > 4) + return codeSize >= 15; + if (representative.externalReads.size() > 4) + return codeSize >= 15; + return codeSize >= 7; + */ +} + +void BlockOutliner::run(Block& _ast, NameDispenser& _nameDispenser) +{ + std::vector blockClasses = BlockClassFinder::run(_ast); + std::map blockToFunctionCall; + std::vector> outlinedBlockClasses; + + for (auto const& blockClass: blockClasses) + { + if (!shallOutline(blockClass)) + continue; + + YulString nameHint = blockClass.nameHint; + if (nameHint.empty()) + nameHint = YulString( + "outlined$" + + to_string(blockClass.members.front().block->location.start) + + "$" + ); + outlinedBlockClasses.emplace_back(&blockClass, _nameDispenser.newName(nameHint)); + + // generate a function call for each block in the class + for (auto const& block: blockClass.members) + { + auto loc = block.block->location; + vector arguments; + vector identifiers; + for (auto const& name: block.externalReferences) + { + if (block.externalAssignments.count(name)) + identifiers.emplace_back(Identifier{loc, name}); + if (block.externalReads.count(name)) + arguments.emplace_back(Identifier{loc, name}); + } + FunctionCall call{ + loc, + Identifier{loc, outlinedBlockClasses.back().second}, + std::move(arguments) + }; + if (identifiers.empty()) + blockToFunctionCall[block.block] = ExpressionStatement{loc, move(call)}; + else + blockToFunctionCall[block.block] = Assignment{ + loc, move(identifiers), make_unique(move(call)) + }; + } + } + + if (!outlinedBlockClasses.empty()) + { + BlockOutliner outliner{std::move(blockToFunctionCall), _nameDispenser}; + Block astCopy = boost::get(outliner(_ast)); + for (auto const& outline: outlinedBlockClasses) + astCopy.statements.emplace_back( + outliner.blockClassToFunction(*outline.first, outline.second) + ); + _ast = std::move(astCopy); + } +} + +Block BlockOutliner::translate(Block const& _block) +{ + auto it = m_blockOutlines.find(&_block); + if (it != m_blockOutlines.end()) + return Block { + _block.location, + make_vector(std::move(it->second)) + }; + return ASTCopier::translate(_block); +} + +FunctionDefinition BlockOutliner::blockClassToFunction( + BlockClass const& _blockClass, + YulString _functionName +) +{ + yulAssert(!_blockClass.members.empty(), ""); + Block const& _block = *_blockClass.members.front().block; + Block body{_block.location, translateVector(_block.statements)}; + + TypedNameList parameters; + TypedNameList returnVariables; + for (auto const& name: _blockClass.members.front().externalReferences) + { + bool isRead = _blockClass.members.front().externalReads.count(name); + bool isWritten = _blockClass.members.front().externalAssignments.count(name); + if (isRead) + parameters.emplace_back(TypedName{_block.location, name, {}}); + if (isWritten) + { + if (!isRead) + returnVariables.emplace_back(TypedName{ + _block.location, + name, + {} + }); + else + { + returnVariables.emplace_back(TypedName{ + _block.location, + m_nameDispenser.newName(name), + {} + }); + body.statements.emplace_back(Assignment{ + _block.location, + {Identifier{_block.location, returnVariables.back().name}}, + make_unique(Identifier{_block.location, name}) + }); + } + } + } + return FunctionDefinition{ + _block.location, + _functionName, + move(parameters), + move(returnVariables), + move(body) + }; +} diff --git a/libyul/optimiser/BlockOutliner.h b/libyul/optimiser/BlockOutliner.h new file mode 100644 index 000000000..8ec6e4b7a --- /dev/null +++ b/libyul/optimiser/BlockOutliner.h @@ -0,0 +1,62 @@ +/* + 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 . +*/ +/** + * Optimiser component that outlines blocks that occur multiple times. + */ +#pragma once + +#include +#include +#include +#include +#include + +namespace yul +{ + +/** + * Optimiser component that outlines blocks that occur multiple times. + * + * Prerequisite: Disambiguator, FunctionHoister and FunctionGrouper + * + */ +class BlockOutliner: public ASTCopier +{ +public: + static void run(Block& _block, NameDispenser& _nameDispenser); + +protected: + Block translate(Block const& _block) override; + +private: + BlockOutliner(std::map _outlines, NameDispenser& _nameDispenser) + : m_nameDispenser(_nameDispenser), m_blockOutlines(std::move(_outlines)) {} + + static bool shallOutline(BlockClass const& _blockClass); + + FunctionDefinition blockClassToFunction( + BlockClass const& _blockClass, + YulString _functionName + ); + + + NameDispenser& m_nameDispenser; + std::map m_blockOutlines; +}; + + +} diff --git a/libyul/optimiser/SyntacticalEquality.cpp b/libyul/optimiser/SyntacticalEquality.cpp index 53f0b029e..53b842a29 100644 --- a/libyul/optimiser/SyntacticalEquality.cpp +++ b/libyul/optimiser/SyntacticalEquality.cpp @@ -30,6 +30,20 @@ using namespace std; using namespace dev; using namespace yul; +SyntacticallyEqual::SyntacticallyEqual( + vector const& _lhsInit, + vector const& _rhsInit +) +{ + yulAssert(_lhsInit.size() == _rhsInit.size(), ""); + for (size_t i = 0; i < _lhsInit.size(); ++i) + { + m_identifiersLHS[_lhsInit[i]] = i; + m_identifiersRHS[_rhsInit[i]] = i; + } + m_idsUsed = _lhsInit.size(); +} + bool SyntacticallyEqual::operator()(Expression const& _lhs, Expression const& _rhs) { return boost::apply_visitor([this](auto&& _lhsExpr, auto&& _rhsExpr) -> bool { diff --git a/libyul/optimiser/SyntacticalEquality.h b/libyul/optimiser/SyntacticalEquality.h index c5e722c74..4fb412281 100644 --- a/libyul/optimiser/SyntacticalEquality.h +++ b/libyul/optimiser/SyntacticalEquality.h @@ -39,6 +39,9 @@ namespace yul class SyntacticallyEqual { public: + SyntacticallyEqual() = default; + SyntacticallyEqual(std::vector const& _lhsInit, std::vector const& _rhsInit); + bool operator()(Expression const& _lhs, Expression const& _rhs); bool operator()(Statement const& _lhs, Statement const& _rhs); diff --git a/test/libyul/YulOptimizerTest.cpp b/test/libyul/YulOptimizerTest.cpp index c2a2b009e..fedc2ff0b 100644 --- a/test/libyul/YulOptimizerTest.cpp +++ b/test/libyul/YulOptimizerTest.cpp @@ -20,6 +20,7 @@ #include #include +#include #include #include #include @@ -116,6 +117,13 @@ TestCase::TestResult YulOptimizerTest::run(ostream& _stream, string const& _line disambiguate(); BlockFlattener{}(*m_ast); } + else if (m_optimizerStep == "blockOutliner") + { + disambiguate(); + (FunctionHoister{})(*m_ast); + NameDispenser nameDispenser{*m_dialect, *m_ast}; + BlockOutliner::run(*m_ast, nameDispenser); + } else if (m_optimizerStep == "varDeclInitializer") VarDeclInitializer{}(*m_ast); else if (m_optimizerStep == "varNameCleaner") diff --git a/test/libyul/yulOptimizerTests/blockOutliner/basic.yul b/test/libyul/yulOptimizerTests/blockOutliner/basic.yul new file mode 100644 index 000000000..91754bb89 --- /dev/null +++ b/test/libyul/yulOptimizerTests/blockOutliner/basic.yul @@ -0,0 +1,91 @@ +{ + { + let a let b let c let d + { + a := mul(c,b) + if lt(a,c) { + a := add(a,c) + } + } + { + if eq(1,2) { + d := mul(a,b) + if lt(d,a) { + d := add(d,a) + } + } + { + d := add(d,a) + } + } + { + c := mul(a,b) + if lt(c,a) { + c := add(c,a) + } + } + } + function f(a, b) -> r { + { + r := mul(a,b) + if lt(r, a) { + r := add(r,a) + } + } + function g(x, y) -> z { + z := mul(x,y) + if lt(z,x) { + z := add(z,x) + } + } + r := g(b,a) + } + function h(a) -> r { + { + r := add(r,a) + } + { + r := add(r,a) + } + } +} +// ==== +// step: blockOutliner +// ---- +// { +// { +// let a +// let b +// let c +// let d +// { a := g_1(a, c, b) } +// { +// if eq(1, 2) { d := g_1(d, a, b) } +// { d := outlined$66$(d, a) } +// } +// { c := g_1(c, a, b) } +// } +// function g(x, y) -> z +// { z := g_1(z, x, y) } +// function f(a_1, b_2) -> r +// { +// { r := g_1(r, a_1, b_2) } +// r := g(b_2, a_1) +// } +// function h(a_3) -> r_4 +// { +// { r_4 := outlined$66$(r_4, a_3) } +// { r_4 := outlined$66$(r_4, a_3) } +// } +// function outlined$66$(a, c) -> a_2 +// { +// a := add(a, c) +// a_2 := a +// } +// function g_1(a, c, b) -> a_4 +// { +// a := mul(c, b) +// if lt(a, c) { a := outlined$66$(a, c) } +// a_4 := a +// } +// } diff --git a/test/libyul/yulOptimizerTests/blockOutliner/break_continue.yul b/test/libyul/yulOptimizerTests/blockOutliner/break_continue.yul new file mode 100644 index 000000000..d499d68d2 --- /dev/null +++ b/test/libyul/yulOptimizerTests/blockOutliner/break_continue.yul @@ -0,0 +1,75 @@ +{ + let a := 1 + let b := 2 + let c := 3 + { + for {} 1 {} { + { a := mul(b,c) } + if gt(a,b) { break } + } + } + { + for {} 1 {} { + { a := mul(b,c) } + if gt(a,b) { break } + } + } + { + for {} 1 {} { + { a := mul(b,c) } + if gt(a,b) { break } + } + } + { + for {} 1 {} { + { a := mul(b,c) } + if gt(a,b) { continue } + } + } + { + for {} 1 {} { + { a := mul(b,c) } + if gt(a,b) { continue } + } + } + { + for {} 1 {} { + { a := mul(b,c) } + if gt(a,b) { continue } + } + } +} +// ==== +// step: blockOutliner +// ---- +// { +// let a := 1 +// let b := 2 +// let c := 3 +// { a := outlined$48$(a, b, c) } +// { a := outlined$48$(a, b, c) } +// { a := outlined$48$(a, b, c) } +// { a := outlined$261$(a, b, c) } +// { a := outlined$261$(a, b, c) } +// { a := outlined$261$(a, b, c) } +// function outlined$69$(b, c) -> a +// { a := mul(b, c) } +// function outlined$48$(a, b, c) -> a_1 +// { +// for { } 1 { } +// { +// { a := outlined$69$(b, c) } +// if gt(a, b) { break } +// } +// a_1 := a +// } +// function outlined$261$(a, b, c) -> a_2 +// { +// for { } 1 { } +// { +// { a := outlined$69$(b, c) } +// if gt(a, b) { continue } +// } +// a_2 := a +// } +// } diff --git a/test/libyul/yulOptimizerTests/blockOutliner/nested.yul b/test/libyul/yulOptimizerTests/blockOutliner/nested.yul new file mode 100644 index 000000000..f4c9a2405 --- /dev/null +++ b/test/libyul/yulOptimizerTests/blockOutliner/nested.yul @@ -0,0 +1,20 @@ +{ + { + function f() -> x { x := 1 } + { mstore(f(), 2) } + { mstore(f(), 2) } + } +} +// ==== +// step: blockOutliner +// ---- +// { +// { +// { outlined$43$() } +// { outlined$43$() } +// } +// function f() -> x +// { x := 1 } +// function outlined$43$() +// { mstore(f(), 2) } +// } diff --git a/test/libyul/yulOptimizerTests/blockOutliner/nested_inner.yul b/test/libyul/yulOptimizerTests/blockOutliner/nested_inner.yul new file mode 100644 index 000000000..764017169 --- /dev/null +++ b/test/libyul/yulOptimizerTests/blockOutliner/nested_inner.yul @@ -0,0 +1,22 @@ +{ + { + function f() -> x { x := 1 } + { { mstore(f(), 2) } } + { { mstore(f(), 2) } } + } +} +// ==== +// step: blockOutliner +// ---- +// { +// { +// { outlined$43$() } +// { outlined$43$() } +// } +// function f() -> x +// { x := 1 } +// function outlined$45$() +// { mstore(f(), 2) } +// function outlined$43$() +// { { outlined$45$() } } +// } diff --git a/test/libyul/yulOptimizerTests/blockOutliner/only_assign.yul b/test/libyul/yulOptimizerTests/blockOutliner/only_assign.yul new file mode 100644 index 000000000..ed441ba91 --- /dev/null +++ b/test/libyul/yulOptimizerTests/blockOutliner/only_assign.yul @@ -0,0 +1,27 @@ +{ + let a + let b + let c + { + a := mul(b,c) + } + { + b := mul(c,a) + } + { + c := mul(a,b) + } +} +// ==== +// step: blockOutliner +// ---- +// { +// let a +// let b +// let c +// { a := outlined$36$(b, c) } +// { b := outlined$36$(c, a) } +// { c := outlined$36$(a, b) } +// function outlined$36$(b, c) -> a +// { a := mul(b, c) } +// }