diff --git a/libyul/CMakeLists.txt b/libyul/CMakeLists.txt index 3c980fbb8..039e24ea9 100644 --- a/libyul/CMakeLists.txt +++ b/libyul/CMakeLists.txt @@ -40,6 +40,8 @@ add_library(yul Dialect.cpp Dialect.h Exceptions.h + FunctionReferenceResolver.cpp + FunctionReferenceResolver.h Object.cpp Object.h ObjectParser.cpp diff --git a/libyul/ControlFlowSideEffectsCollector.cpp b/libyul/ControlFlowSideEffectsCollector.cpp index bdcf70a78..6e96712a0 100644 --- a/libyul/ControlFlowSideEffectsCollector.cpp +++ b/libyul/ControlFlowSideEffectsCollector.cpp @@ -20,6 +20,7 @@ #include #include +#include #include #include @@ -35,16 +36,15 @@ using namespace solidity::yul; ControlFlowBuilder::ControlFlowBuilder(Block const& _ast) { - for (auto const& statement: _ast.statements) - if (auto const* function = get_if(&statement)) - (*this)(*function); + m_currentNode = newNode(); + (*this)(_ast); } void ControlFlowBuilder::operator()(FunctionCall const& _functionCall) { walkVector(_functionCall.arguments | ranges::views::reverse); newConnectedNode(); - m_currentNode->functionCall = _functionCall.functionName.name; + m_currentNode->functionCall = &_functionCall; } void ControlFlowBuilder::operator()(If const& _if) @@ -78,7 +78,9 @@ void ControlFlowBuilder::operator()(Switch const& _switch) void ControlFlowBuilder::operator()(FunctionDefinition const& _function) { ScopedSaveAndRestore currentNode(m_currentNode, nullptr); - yulAssert(!m_leave && !m_break && !m_continue, "Function hoister has not been used."); + ScopedSaveAndRestore leave(m_leave, nullptr); + ScopedSaveAndRestore _break(m_break, nullptr); + ScopedSaveAndRestore _continue(m_continue, nullptr); FunctionFlow flow; flow.exit = newNode(); @@ -90,7 +92,7 @@ void ControlFlowBuilder::operator()(FunctionDefinition const& _function) m_currentNode->successors.emplace_back(flow.exit); - m_functionFlows[_function.name] = move(flow); + m_functionFlows[&_function] = move(flow); m_leave = nullptr; } @@ -164,14 +166,17 @@ ControlFlowSideEffectsCollector::ControlFlowSideEffectsCollector( Block const& _ast ): m_dialect(_dialect), - m_cfgBuilder(_ast) + m_cfgBuilder(_ast), + m_functionReferences(FunctionReferenceResolver{_ast}.references()) { - for (auto&& [name, flow]: m_cfgBuilder.functionFlows()) + for (auto&& [function, flow]: m_cfgBuilder.functionFlows()) { yulAssert(!flow.entry->functionCall); - m_processedNodes[name] = {}; - m_pendingNodes[name].push_front(flow.entry); - m_functionSideEffects[name] = {false, false, false}; + yulAssert(function); + m_processedNodes[function] = {}; + m_pendingNodes[function].push_front(flow.entry); + m_functionSideEffects[function] = {false, false, false}; + m_functionCalls[function] = {}; } // Process functions while we have progress. For now, we are only interested @@ -180,8 +185,8 @@ ControlFlowSideEffectsCollector::ControlFlowSideEffectsCollector( while (progress) { progress = false; - for (auto const& functionName: m_pendingNodes | ranges::views::keys) - if (processFunction(functionName)) + for (FunctionDefinition const* function: m_pendingNodes | ranges::views::keys) + if (processFunction(*function)) progress = true; } @@ -190,57 +195,64 @@ ControlFlowSideEffectsCollector::ControlFlowSideEffectsCollector( // If we have not set `canContinue` by now, the function's exit // is not reachable. - for (auto&& [functionName, calls]: m_functionCalls) + // Now it is sufficient to handle the reachable function calls (`m_functionCalls`), + // we do not have to consider the control-flow graph anymore. + for (auto&& [function, calls]: m_functionCalls) { - ControlFlowSideEffects& sideEffects = m_functionSideEffects[functionName]; - auto _visit = [&, visited = std::set{}](YulString _function, auto&& _recurse) mutable { - if (sideEffects.canTerminate && sideEffects.canRevert) + yulAssert(function); + ControlFlowSideEffects& functionSideEffects = m_functionSideEffects[function]; + auto _visit = [&, visited = std::set{}](FunctionDefinition const& _function, auto&& _recurse) mutable { + // Worst side-effects already, stop searching. + if (functionSideEffects.canTerminate && functionSideEffects.canRevert) return; - if (!visited.insert(_function).second) + if (!visited.insert(&_function).second) return; - ControlFlowSideEffects const* calledSideEffects = nullptr; - if (BuiltinFunction const* f = _dialect.builtin(_function)) - calledSideEffects = &f->controlFlowSideEffects; - else - calledSideEffects = &m_functionSideEffects.at(_function); + for (FunctionCall const* call: m_functionCalls.at(&_function)) + { + ControlFlowSideEffects const& calledSideEffects = sideEffects(*call); + if (calledSideEffects.canTerminate) + functionSideEffects.canTerminate = true; + if (calledSideEffects.canRevert) + functionSideEffects.canRevert = true; - if (calledSideEffects->canTerminate) - sideEffects.canTerminate = true; - if (calledSideEffects->canRevert) - sideEffects.canRevert = true; - - set emptySet; - for (YulString callee: util::valueOrDefault(m_functionCalls, _function, emptySet)) - _recurse(callee, _recurse); + if (m_functionReferences.count(call)) + _recurse(*m_functionReferences.at(call), _recurse); + } }; - for (auto const& call: calls) - _visit(call, _visit); + _visit(*function, _visit); } - } -bool ControlFlowSideEffectsCollector::processFunction(YulString _name) +map ControlFlowSideEffectsCollector::functionSideEffectsNamed() const +{ + map result; + for (auto&& [function, sideEffects]: m_functionSideEffects) + yulAssert(result.insert({function->name, sideEffects}).second); + return result; +} + +bool ControlFlowSideEffectsCollector::processFunction(FunctionDefinition const& _function) { bool progress = false; - while (ControlFlowNode const* node = nextProcessableNode(_name)) + while (ControlFlowNode const* node = nextProcessableNode(_function)) { - if (node == m_cfgBuilder.functionFlows().at(_name).exit) + if (node == m_cfgBuilder.functionFlows().at(&_function).exit) { - m_functionSideEffects[_name].canContinue = true; + m_functionSideEffects[&_function].canContinue = true; return true; } for (ControlFlowNode const* s: node->successors) - recordReachabilityAndQueue(_name, s); + recordReachabilityAndQueue(_function, s); progress = true; } return progress; } -ControlFlowNode const* ControlFlowSideEffectsCollector::nextProcessableNode(YulString _functionName) +ControlFlowNode const* ControlFlowSideEffectsCollector::nextProcessableNode(FunctionDefinition const& _function) { - std::list& nodes = m_pendingNodes[_functionName]; + std::list& nodes = m_pendingNodes[&_function]; auto it = ranges::find_if(nodes, [this](ControlFlowNode const* _node) { return !_node->functionCall || sideEffects(*_node->functionCall).canContinue; }); @@ -252,22 +264,22 @@ ControlFlowNode const* ControlFlowSideEffectsCollector::nextProcessableNode(YulS return node; } -ControlFlowSideEffects const& ControlFlowSideEffectsCollector::sideEffects(YulString _functionName) const +ControlFlowSideEffects const& ControlFlowSideEffectsCollector::sideEffects(FunctionCall const& _call) const { - if (auto const* builtin = m_dialect.builtin(_functionName)) + if (auto const* builtin = m_dialect.builtin(_call.functionName.name)) return builtin->controlFlowSideEffects; else - return m_functionSideEffects.at(_functionName); + return m_functionSideEffects.at(m_functionReferences.at(&_call)); } void ControlFlowSideEffectsCollector::recordReachabilityAndQueue( - YulString _functionName, + FunctionDefinition const& _function, ControlFlowNode const* _node ) { if (_node->functionCall) - m_functionCalls[_functionName].insert(*_node->functionCall); - if (m_processedNodes[_functionName].insert(_node).second) - m_pendingNodes.at(_functionName).push_front(_node); + m_functionCalls[&_function].insert(_node->functionCall); + if (m_processedNodes[&_function].insert(_node).second) + m_pendingNodes.at(&_function).push_front(_node); } diff --git a/libyul/ControlFlowSideEffectsCollector.h b/libyul/ControlFlowSideEffectsCollector.h index f130294ba..a4ab2bfae 100644 --- a/libyul/ControlFlowSideEffectsCollector.h +++ b/libyul/ControlFlowSideEffectsCollector.h @@ -34,8 +34,8 @@ struct Dialect; struct ControlFlowNode { std::vector successors; - /// Name of the called function if the node calls a function. - std::optional functionCall; + /// Function call AST node, if present. + FunctionCall const* functionCall = nullptr; }; /** @@ -56,7 +56,7 @@ public: /// Computes the control-flows of all function defined in the block. /// Assumes the functions are hoisted to the topmost block. explicit ControlFlowBuilder(Block const& _ast); - std::map const& functionFlows() const { return m_functionFlows; } + std::map const& functionFlows() const { return m_functionFlows; } private: using ASTWalker::operator(); @@ -79,12 +79,14 @@ private: ControlFlowNode const* m_break = nullptr; ControlFlowNode const* m_continue = nullptr; - std::map m_functionFlows; + std::map m_functionFlows; }; /** - * Requires: Disambiguator, Function Hoister. + * Computes control-flow side-effects for user-defined functions. + * Source does not have to be disambiguated, unless you want the side-effects + * based on function names. */ class ControlFlowSideEffectsCollector { @@ -94,36 +96,43 @@ public: Block const& _ast ); - std::map const& functionSideEffects() const + std::map const& functionSideEffects() const { return m_functionSideEffects; } + /// Returns the side effects by function name, requires unique function names. + std::map functionSideEffectsNamed() const; private: /// @returns false if nothing could be processed. - bool processFunction(YulString _name); + bool processFunction(FunctionDefinition const& _function); /// @returns the next pending node of the function that is not /// a function call to a function that might not continue. /// De-queues the node or returns nullptr if no such node is found. - ControlFlowNode const* nextProcessableNode(YulString _functionName); + ControlFlowNode const* nextProcessableNode(FunctionDefinition const& _function); /// @returns the side-effects of either a builtin call or a user defined function /// call (as far as already computed). - ControlFlowSideEffects const& sideEffects(YulString _functionName) const; + ControlFlowSideEffects const& sideEffects(FunctionCall const& _call) const; /// Queues the given node to be processed (if not already visited) /// and if it is a function call, records that `_functionName` calls /// `*_node->functionCall`. - void recordReachabilityAndQueue(YulString _functionName, ControlFlowNode const* _node); + void recordReachabilityAndQueue(FunctionDefinition const& _function, ControlFlowNode const* _node); Dialect const& m_dialect; ControlFlowBuilder m_cfgBuilder; - std::map m_functionSideEffects; - std::map> m_pendingNodes; - std::map> m_processedNodes; - /// `x` is in `m_functionCalls[y]` if a direct call to `x` is reachable inside `y` - std::map> m_functionCalls; + /// Function references, but only for calls to user-defined functions. + std::map m_functionReferences; + /// Side effects of user-defined functions, is being constructod. + std::map m_functionSideEffects; + /// Control flow nodes still to process, per function. + std::map> m_pendingNodes; + /// Control flow nodes already processed, per function. + std::map> m_processedNodes; + /// Set of reachable function calls nodes in each function (including calls to builtins). + std::map> m_functionCalls; }; diff --git a/libyul/FunctionReferenceResolver.cpp b/libyul/FunctionReferenceResolver.cpp new file mode 100644 index 000000000..5df94237a --- /dev/null +++ b/libyul/FunctionReferenceResolver.cpp @@ -0,0 +1,60 @@ +/* + 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 + +using namespace std; +using namespace solidity::yul; +using namespace solidity::util; + +FunctionReferenceResolver::FunctionReferenceResolver(Block const& _ast) +{ + (*this)(_ast); + yulAssert(m_scopes.empty()); +} + +void FunctionReferenceResolver::operator()(FunctionCall const& _functionCall) +{ + for (auto&& scope: m_scopes | ranges::views::reverse) + if (FunctionDefinition const** function = util::valueOrNullptr(scope, _functionCall.functionName.name)) + { + m_functionReferences[&_functionCall] = *function; + break; + } + + // If we did not find anything, it was a builtin call. + + ASTWalker::operator()(_functionCall); +} + +void FunctionReferenceResolver::operator()(Block const& _block) +{ + m_scopes.emplace_back(); + for (auto const& statement: _block.statements) + if (auto const* function = get_if(&statement)) + m_scopes.back()[function->name] = function; + + ASTWalker::operator()(_block); + + m_scopes.pop_back(); +} diff --git a/libyul/FunctionReferenceResolver.h b/libyul/FunctionReferenceResolver.h new file mode 100644 index 000000000..8c1385adb --- /dev/null +++ b/libyul/FunctionReferenceResolver.h @@ -0,0 +1,48 @@ +/* + 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::yul +{ + +/** + * Resolves references to user-defined functions in function calls. + * Assumes the code is correct, i.e. does not check for references to be valid or unique. + * + * Be careful not to iterate over the result - it is not deterministic. + */ +class FunctionReferenceResolver: private ASTWalker +{ +public: + explicit FunctionReferenceResolver(Block const& _ast); + std::map const& references() const { return m_functionReferences; } + +private: + using ASTWalker::operator(); + void operator()(FunctionCall const& _functionCall) override; + void operator()(Block const& _block) override; + + std::map m_functionReferences; + std::vector> m_scopes; +}; + + +} diff --git a/libyul/optimiser/ConditionalSimplifier.cpp b/libyul/optimiser/ConditionalSimplifier.cpp index 3a2e8aa4e..62d36520a 100644 --- a/libyul/optimiser/ConditionalSimplifier.cpp +++ b/libyul/optimiser/ConditionalSimplifier.cpp @@ -29,8 +29,10 @@ using namespace solidity::util; void ConditionalSimplifier::run(OptimiserStepContext& _context, Block& _ast) { - ControlFlowSideEffectsCollector sideEffects(_context.dialect, _ast); - ConditionalSimplifier{_context.dialect, sideEffects.functionSideEffects()}(_ast); + ConditionalSimplifier{ + _context.dialect, + ControlFlowSideEffectsCollector{_context.dialect, _ast}.functionSideEffectsNamed() + }(_ast); } void ConditionalSimplifier::operator()(Switch& _switch) diff --git a/libyul/optimiser/ConditionalSimplifier.h b/libyul/optimiser/ConditionalSimplifier.h index 57a6696c4..5df56476a 100644 --- a/libyul/optimiser/ConditionalSimplifier.h +++ b/libyul/optimiser/ConditionalSimplifier.h @@ -62,12 +62,12 @@ public: private: explicit ConditionalSimplifier( Dialect const& _dialect, - std::map const& _sideEffects + std::map _sideEffects ): - m_dialect(_dialect), m_functionSideEffects(_sideEffects) + m_dialect(_dialect), m_functionSideEffects(move(_sideEffects)) {} Dialect const& m_dialect; - std::map const& m_functionSideEffects; + std::map m_functionSideEffects; }; } diff --git a/libyul/optimiser/ConditionalUnsimplifier.cpp b/libyul/optimiser/ConditionalUnsimplifier.cpp index 752e06918..ef640ed7c 100644 --- a/libyul/optimiser/ConditionalUnsimplifier.cpp +++ b/libyul/optimiser/ConditionalUnsimplifier.cpp @@ -30,8 +30,10 @@ using namespace solidity::util; void ConditionalUnsimplifier::run(OptimiserStepContext& _context, Block& _ast) { - ControlFlowSideEffectsCollector sideEffects(_context.dialect, _ast); - ConditionalUnsimplifier{_context.dialect, sideEffects.functionSideEffects()}(_ast); + ConditionalUnsimplifier{ + _context.dialect, + ControlFlowSideEffectsCollector{_context.dialect, _ast}.functionSideEffectsNamed() + }(_ast); } void ConditionalUnsimplifier::operator()(Switch& _switch) diff --git a/libyul/optimiser/DeadCodeEliminator.cpp b/libyul/optimiser/DeadCodeEliminator.cpp index ad127a910..af8454684 100644 --- a/libyul/optimiser/DeadCodeEliminator.cpp +++ b/libyul/optimiser/DeadCodeEliminator.cpp @@ -40,7 +40,7 @@ void DeadCodeEliminator::run(OptimiserStepContext& _context, Block& _ast) ControlFlowSideEffectsCollector sideEffects(_context.dialect, _ast); DeadCodeEliminator{ _context.dialect, - sideEffects.functionSideEffects() + sideEffects.functionSideEffectsNamed() }(_ast); } diff --git a/libyul/optimiser/DeadCodeEliminator.h b/libyul/optimiser/DeadCodeEliminator.h index 98202fc94..2c166a836 100644 --- a/libyul/optimiser/DeadCodeEliminator.h +++ b/libyul/optimiser/DeadCodeEliminator.h @@ -23,6 +23,7 @@ #include #include +#include #include #include @@ -31,7 +32,6 @@ namespace solidity::yul { struct Dialect; struct OptimiserStepContext; -struct ControlFlowSideEffects; /** * Optimisation stage that removes unreachable code @@ -62,11 +62,11 @@ public: private: DeadCodeEliminator( Dialect const& _dialect, - std::map const& _sideEffects - ): m_dialect(_dialect), m_functionSideEffects(_sideEffects) {} + std::map _sideEffects + ): m_dialect(_dialect), m_functionSideEffects(move(_sideEffects)) {} Dialect const& m_dialect; - std::map const& m_functionSideEffects; + std::map m_functionSideEffects; }; } diff --git a/test/libyul/ControlFlowSideEffectsTest.cpp b/test/libyul/ControlFlowSideEffectsTest.cpp index aff3c564d..ce6c18c35 100644 --- a/test/libyul/ControlFlowSideEffectsTest.cpp +++ b/test/libyul/ControlFlowSideEffectsTest.cpp @@ -22,6 +22,7 @@ #include #include +#include #include #include #include @@ -61,19 +62,15 @@ TestCase::TestResult ControlFlowSideEffectsTest::run(ostream& _stream, string co if (!obj.code) BOOST_THROW_EXCEPTION(runtime_error("Parsing input failed.")); - std::map sideEffects = - ControlFlowSideEffectsCollector( - EVMDialect::strictAssemblyForEVMObjects(langutil::EVMVersion()), - *obj.code - ).functionSideEffects(); - - std::map controlFlowSideEffectsStr; - for (auto&& [fun, effects]: sideEffects) - controlFlowSideEffectsStr[fun.str()] = toString(effects); - + ControlFlowSideEffectsCollector sideEffects( + EVMDialect::strictAssemblyForEVMObjects(langutil::EVMVersion()), + *obj.code + ); m_obtainedResult.clear(); - for (auto&& [functionName, effect]: controlFlowSideEffectsStr) - m_obtainedResult += functionName + (effect.empty() ? ":" : ": " + effect) + "\n"; + forEach(*obj.code, [&](FunctionDefinition const& _fun) { + string effectStr = toString(sideEffects.functionSideEffects().at(&_fun)); + m_obtainedResult += _fun.name.str() + (effectStr.empty() ? ":" : ": " + effectStr) + "\n"; + }); return checkResult(_stream, _linePrefix, _formatted); } diff --git a/test/libyul/controlFlowSideEffects/nondisambiguated.yul b/test/libyul/controlFlowSideEffects/nondisambiguated.yul new file mode 100644 index 000000000..4cc68c140 --- /dev/null +++ b/test/libyul/controlFlowSideEffects/nondisambiguated.yul @@ -0,0 +1,23 @@ +{ + function a() { + { + function b() { if calldataload(0) { return(0, 0) } } + b() + } + { + function b() { revert(0, 0) } + b() + } + } + { + function b() { + leave + revert(0, 0) + } + } +} +// ---- +// a: can terminate, can revert +// b: can terminate, can continue +// b: can revert +// b: can continue diff --git a/test/libyul/controlFlowSideEffects/recursion.yul b/test/libyul/controlFlowSideEffects/recursion.yul index c4176d502..69cd4a2da 100644 --- a/test/libyul/controlFlowSideEffects/recursion.yul +++ b/test/libyul/controlFlowSideEffects/recursion.yul @@ -31,7 +31,7 @@ // b: can revert // c: // d: -// reg: can continue // x: // y: // z: +// reg: can continue