From facd98e426fa6a284686d8dbbcdc1a64df9c7aa9 Mon Sep 17 00:00:00 2001 From: Daniel Kirchner Date: Fri, 11 Jun 2021 19:17:41 +0200 Subject: [PATCH] Control flow graph for Yul. --- libsolutil/cxx20.h | 11 + libyul/CMakeLists.txt | 3 + libyul/Scope.cpp | 3 +- libyul/Scope.h | 3 +- libyul/backends/evm/ControlFlowGraph.h | 193 +++++++ .../backends/evm/ControlFlowGraphBuilder.cpp | 484 ++++++++++++++++++ libyul/backends/evm/ControlFlowGraphBuilder.h | 80 +++ 7 files changed, 775 insertions(+), 2 deletions(-) create mode 100644 libyul/backends/evm/ControlFlowGraph.h create mode 100644 libyul/backends/evm/ControlFlowGraphBuilder.cpp create mode 100644 libyul/backends/evm/ControlFlowGraphBuilder.h diff --git a/libsolutil/cxx20.h b/libsolutil/cxx20.h index bbe9817aa..c65b0454b 100644 --- a/libsolutil/cxx20.h +++ b/libsolutil/cxx20.h @@ -49,4 +49,15 @@ erase_if(std::unordered_map& _c, Pred _pred) return old_size - _c.size(); } +// Taken from https://en.cppreference.com/w/cpp/container/vector/erase2 +template +constexpr typename std::vector::size_type +erase_if(std::vector& c, Pred pred) +{ + auto it = std::remove_if(c.begin(), c.end(), pred); + auto r = std::distance(it, c.end()); + c.erase(it, c.end()); + return static_cast::size_type>(r); +} + } diff --git a/libyul/CMakeLists.txt b/libyul/CMakeLists.txt index 59fb6d468..7b9d102e8 100644 --- a/libyul/CMakeLists.txt +++ b/libyul/CMakeLists.txt @@ -53,6 +53,9 @@ add_library(yul backends/evm/AsmCodeGen.h backends/evm/ConstantOptimiser.cpp backends/evm/ConstantOptimiser.h + backends/evm/ControlFlowGraph.h + backends/evm/ControlFlowGraphBuilder.cpp + backends/evm/ControlFlowGraphBuilder.h backends/evm/EthAssemblyAdapter.cpp backends/evm/EthAssemblyAdapter.h backends/evm/EVMCodeTransform.cpp diff --git a/libyul/Scope.cpp b/libyul/Scope.cpp index 3425e81ae..4a9520f98 100644 --- a/libyul/Scope.cpp +++ b/libyul/Scope.cpp @@ -32,6 +32,7 @@ bool Scope::registerVariable(YulString _name, YulType const& _type) return false; Variable variable; variable.type = _type; + variable.name = _name; identifiers[_name] = variable; return true; } @@ -40,7 +41,7 @@ bool Scope::registerFunction(YulString _name, std::vector _arguments, s { if (exists(_name)) return false; - identifiers[_name] = Function{std::move(_arguments), std::move(_returns)}; + identifiers[_name] = Function{std::move(_arguments), std::move(_returns), _name}; return true; } diff --git a/libyul/Scope.h b/libyul/Scope.h index a64906f2d..b7397c888 100644 --- a/libyul/Scope.h +++ b/libyul/Scope.h @@ -37,11 +37,12 @@ struct Scope { using YulType = YulString; - struct Variable { YulType type; }; + struct Variable { YulType type; YulString name; }; struct Function { std::vector arguments; std::vector returns; + YulString name; }; using Identifier = std::variant; diff --git a/libyul/backends/evm/ControlFlowGraph.h b/libyul/backends/evm/ControlFlowGraph.h new file mode 100644 index 000000000..46224edae --- /dev/null +++ b/libyul/backends/evm/ControlFlowGraph.h @@ -0,0 +1,193 @@ +/* + 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 +/** + * Control flow graph and stack layout structures used during code generation. + */ + +#pragma once + +#include +#include +#include +#include + +#include +#include +#include + +namespace solidity::yul +{ + +/// The label pushed as return label before a function call, i.e. the label the call is supposed to return to. +struct FunctionCallReturnLabelSlot +{ + std::reference_wrapper call; + bool operator==(FunctionCallReturnLabelSlot const& _rhs) const { return &call.get() == &_rhs.call.get(); } + bool operator<(FunctionCallReturnLabelSlot const& _rhs) const { return &call.get() < &_rhs.call.get(); } + static constexpr bool canBeFreelyGenerated = true; +}; +/// The return label of a function while generating the code of the function body. +struct FunctionReturnLabelSlot +{ + bool operator==(FunctionReturnLabelSlot const&) const { return true; } + bool operator<(FunctionReturnLabelSlot const&) const { return false; } + static constexpr bool canBeFreelyGenerated = false; +}; +/// A slot containing the current value of a particular variable. +struct VariableSlot +{ + std::reference_wrapper variable; + std::shared_ptr debugData{}; + bool operator==(VariableSlot const& _rhs) const { return &variable.get() == &_rhs.variable.get(); } + bool operator<(VariableSlot const& _rhs) const { return &variable.get() < &_rhs.variable.get(); } + static constexpr bool canBeFreelyGenerated = false; +}; +/// A slot containing a literal value. +struct LiteralSlot +{ + u256 value; + std::shared_ptr debugData{}; + bool operator==(LiteralSlot const& _rhs) const { return value == _rhs.value; } + bool operator<(LiteralSlot const& _rhs) const { return value < _rhs.value; } + static constexpr bool canBeFreelyGenerated = true; +}; +/// A slot containing the idx-th return value of a previous call. +struct TemporarySlot +{ + /// The call that returned this slot. + std::reference_wrapper call; + /// Specifies to which of the values returned by the call this slot refers. + /// index == 0 refers to the slot deepest in the stack after the call. + size_t index = 0; + bool operator==(TemporarySlot const& _rhs) const { return &call.get() == &_rhs.call.get() && index == _rhs.index; } + bool operator<(TemporarySlot const& _rhs) const { return std::make_pair(&call.get(), index) < std::make_pair(&_rhs.call.get(), _rhs.index); } + static constexpr bool canBeFreelyGenerated = false; +}; +/// A slot containing an arbitrary value that is always eventually popped and never used. +/// Used to maintain stack balance on control flow joins. +struct JunkSlot +{ + bool operator==(JunkSlot const&) const { return true; } + bool operator<(JunkSlot const&) const { return false; } + static constexpr bool canBeFreelyGenerated = true; +}; +using StackSlot = std::variant; +/// The stack top is usually the last element of the vector. +using Stack = std::vector; + +/// @returns true if @a _slot can be generated on the stack at any time. +inline bool canBeFreelyGenerated(StackSlot const& _slot) +{ + return std::visit([](auto const& _typedSlot) { return std::decay_t::canBeFreelyGenerated; }, _slot); +} + +/// Control flow graph consisting of ``DFG::BasicBlock``s connected by control flow. +struct CFG +{ + explicit CFG() {} + CFG(CFG const&) = delete; + CFG(CFG&&) = delete; + CFG& operator=(CFG const&) = delete; + CFG& operator=(CFG&&) = delete; + + struct BuiltinCall + { + std::shared_ptr debugData; + std::reference_wrapper builtin; + std::reference_wrapper functionCall; + /// Number of proper arguments with a position on the stack, excluding literal arguments. + size_t arguments = 0; + }; + struct FunctionCall + { + std::shared_ptr debugData; + std::reference_wrapper function; + std::reference_wrapper functionCall; + }; + struct Assignment + { + std::shared_ptr debugData; + /// The variables being assigned to also occur as ``output`` in the ``Operation`` containing + /// the assignment, but are also stored here for convenience. + std::vector variables; + }; + + struct Operation + { + /// Stack slots this operation expects at the top of the stack and consumes. + Stack input; + /// Stack slots this operation leaves on the stack as output. + Stack output; + std::variant operation; + }; + + struct FunctionInfo; + /// A basic control flow block containing ``Operation``s acting on the stack. + /// Maintains a list of entry blocks and a typed exit. + struct BasicBlock + { + std::vector entries; + std::vector operations; + struct MainExit {}; + struct ConditionalJump + { + StackSlot condition; + BasicBlock* nonZero = nullptr; + BasicBlock* zero = nullptr; + }; + struct Jump + { + BasicBlock* target = nullptr; + /// The only backwards jumps are jumps from loop post to loop condition. + bool backwards = false; + }; + struct FunctionReturn { CFG::FunctionInfo* info = nullptr; }; + struct Terminated {}; + std::variant exit = MainExit{}; + }; + + struct FunctionInfo + { + std::shared_ptr debugData; + Scope::Function const& function; + BasicBlock* entry = nullptr; + std::vector parameters; + std::vector returnVariables; + }; + + /// The main entry point, i.e. the start of the outermost Yul block. + BasicBlock* entry = nullptr; + /// Subgraphs for functions. + std::map functionInfo; + /// List of functions in order of declaration. + std::list functions; + + /// Container for blocks for explicit ownership. + std::list blocks; + /// Container for creates variables for explicit ownership. + std::list ghostVariables; + /// Container for creates calls for explicit ownership. + std::list ghostCalls; + + BasicBlock& makeBlock() + { + return blocks.emplace_back(BasicBlock{}); + } +}; + +} diff --git a/libyul/backends/evm/ControlFlowGraphBuilder.cpp b/libyul/backends/evm/ControlFlowGraphBuilder.cpp new file mode 100644 index 000000000..eaad98cb4 --- /dev/null +++ b/libyul/backends/evm/ControlFlowGraphBuilder.cpp @@ -0,0 +1,484 @@ +/* + 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 +/** + * Transformation of a Yul AST into a control flow graph. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace solidity; +using namespace solidity::yul; +using namespace std; + +std::unique_ptr ControlFlowGraphBuilder::build( + AsmAnalysisInfo& _analysisInfo, + Dialect const& _dialect, + Block const& _block +) +{ + auto result = std::make_unique(); + result->entry = &result->makeBlock(); + + ControlFlowGraphBuilder builder(*result, _analysisInfo, _dialect); + builder.m_currentBlock = result->entry; + builder(_block); + + // Determine which blocks are reachable from the entry. + util::BreadthFirstSearch reachabilityCheck{{result->entry}}; + ranges::actions::push_back( + reachabilityCheck.verticesToTraverse, + result->functionInfo | ranges::views::values | ranges::views::transform( + [](auto&& _function) { return _function.entry; } + ) + ); + reachabilityCheck.run([&](CFG::BasicBlock* _node, auto&& _addChild) { + visit(util::GenericVisitor{ + [&](CFG::BasicBlock::Jump& _jump) { + _addChild(_jump.target); + }, + [&](CFG::BasicBlock::ConditionalJump& _jump) { + _addChild(_jump.zero); + _addChild(_jump.nonZero); + }, + [](CFG::BasicBlock::FunctionReturn&) {}, + [](CFG::BasicBlock::Terminated&) {}, + [](CFG::BasicBlock::MainExit&) {} + }, _node->exit); + }); + + // Remove all entries from unreachable nodes from the graph. + for (auto* node: reachabilityCheck.visited) + cxx20::erase_if(node->entries, [&](CFG::BasicBlock* entry) -> bool { + return !reachabilityCheck.visited.count(entry); + }); + + // TODO: It might be worthwhile to run some further simplifications on the graph itself here. + // E.g. if there is a jump to a node that has the jumping node as its only entry, the nodes can be fused, etc. + + return result; +} + +ControlFlowGraphBuilder::ControlFlowGraphBuilder( + CFG& _graph, + AsmAnalysisInfo& _analysisInfo, + Dialect const& _dialect +): +m_graph(_graph), +m_info(_analysisInfo), +m_dialect(_dialect) +{ +} + +StackSlot ControlFlowGraphBuilder::operator()(Literal const& _literal) +{ + return LiteralSlot{valueOfLiteral(_literal), _literal.debugData}; +} + +StackSlot ControlFlowGraphBuilder::operator()(Identifier const& _identifier) +{ + return VariableSlot{lookupVariable(_identifier.name), _identifier.debugData}; +} + +StackSlot ControlFlowGraphBuilder::operator()(Expression const& _expression) +{ + return std::visit(*this, _expression); +} + +CFG::Operation& ControlFlowGraphBuilder::visitFunctionCall(FunctionCall const& _call) +{ + yulAssert(m_scope, ""); + yulAssert(m_currentBlock, ""); + + if (BuiltinFunction const* builtin = m_dialect.builtin(_call.functionName.name)) + { + CFG::Operation& operation = m_currentBlock->operations.emplace_back(CFG::Operation{ + // input + _call.arguments | + ranges::views::enumerate | + ranges::views::reverse | + ranges::views::filter(util::mapTuple([&](size_t idx, auto const&) { + return !builtin->literalArgument(idx).has_value(); + })) | + ranges::views::values | + ranges::views::transform(std::ref(*this)) | + ranges::to, + // output + ranges::views::iota(0u, builtin->returns.size()) | ranges::views::transform([&](size_t _i) { + return TemporarySlot{_call, _i}; + }) | ranges::to, + // operation + CFG::BuiltinCall{_call.debugData, *builtin, _call} + }); + std::get(operation.operation).arguments = operation.input.size(); + return operation; + } + else + { + Scope::Function* function = nullptr; + yulAssert(m_scope->lookup(_call.functionName.name, util::GenericVisitor{ + [](Scope::Variable&) { yulAssert(false, "Expected function name."); }, + [&](Scope::Function& _function) { function = &_function; } + }), "Function name not found."); + yulAssert(function, ""); + return m_currentBlock->operations.emplace_back(CFG::Operation{ + // input + ranges::concat_view( + ranges::views::single(StackSlot{FunctionCallReturnLabelSlot{_call}}), + _call.arguments | ranges::views::reverse | ranges::views::transform(std::ref(*this)) + ) | ranges::to, + // output + ranges::views::iota(0u, function->returns.size()) | ranges::views::transform([&](size_t _i) { + return TemporarySlot{_call, _i}; + }) | ranges::to, + // operation + CFG::FunctionCall{_call.debugData, *function, _call} + }); + } +} + +StackSlot ControlFlowGraphBuilder::operator()(FunctionCall const& _call) +{ + CFG::Operation& operation = visitFunctionCall(_call); + yulAssert(operation.output.size() == 1, ""); + return operation.output.front(); +} + +void ControlFlowGraphBuilder::operator()(VariableDeclaration const& _varDecl) +{ + yulAssert(m_currentBlock, ""); + auto declaredVariables = _varDecl.variables | ranges::views::transform([&](TypedName const& _var) { + return VariableSlot{lookupVariable(_var.name), _var.debugData}; + }) | ranges::to; + Stack input; + if (_varDecl.value) + input = std::visit(util::GenericVisitor{ + [&](FunctionCall const& _call) -> Stack { + CFG::Operation& operation = visitFunctionCall(_call); + yulAssert(declaredVariables.size() == operation.output.size(), ""); + return operation.output; + }, + [&](auto const& _identifierOrLiteral) -> Stack{ + yulAssert(declaredVariables.size() == 1, ""); + return {(*this)(_identifierOrLiteral)}; + } + }, *_varDecl.value); + else + input = ranges::views::iota(0u, _varDecl.variables.size()) | ranges::views::transform([&](size_t) { + return LiteralSlot{0, _varDecl.debugData}; + }) | ranges::to; + m_currentBlock->operations.emplace_back(CFG::Operation{ + std::move(input), + declaredVariables | ranges::to, + CFG::Assignment{_varDecl.debugData, declaredVariables} + }); +} +void ControlFlowGraphBuilder::operator()(Assignment const& _assignment) +{ + vector assignedVariables = _assignment.variableNames | ranges::views::transform([&](Identifier const& _var) { + return VariableSlot{lookupVariable(_var.name), _var.debugData}; + }) | ranges::to>; + + yulAssert(m_currentBlock, ""); + m_currentBlock->operations.emplace_back(CFG::Operation{ + // input + std::visit(util::GenericVisitor{ + [&](FunctionCall const& _call) -> Stack { + CFG::Operation& operation = visitFunctionCall(_call); + yulAssert(assignedVariables.size() == operation.output.size(), ""); + return operation.output; + }, + [&](auto const& _identifierOrLiteral) -> Stack { + yulAssert(assignedVariables.size() == 1, ""); + return {(*this)(_identifierOrLiteral)}; + } + }, *_assignment.value), + // output + assignedVariables | ranges::to, + // operation + CFG::Assignment{_assignment.debugData, assignedVariables} + }); +} +void ControlFlowGraphBuilder::operator()(ExpressionStatement const& _exprStmt) +{ + yulAssert(m_currentBlock, ""); + std::visit(util::GenericVisitor{ + [&](FunctionCall const& _call) { + CFG::Operation& operation = visitFunctionCall(_call); + yulAssert(operation.output.empty(), ""); + }, + [&](auto const&) { yulAssert(false, ""); } + }, _exprStmt.expression); + + // TODO: Ideally this would be done on the expression label and for all functions that always revert, + // not only for builtins. + if (auto const* funCall = get_if(&_exprStmt.expression)) + if (BuiltinFunction const* builtin = m_dialect.builtin(funCall->functionName.name)) + if (builtin->controlFlowSideEffects.terminates) + { + m_currentBlock->exit = CFG::BasicBlock::Terminated{}; + m_currentBlock = &m_graph.makeBlock(); + } +} + +void ControlFlowGraphBuilder::operator()(Block const& _block) +{ + ScopedSaveAndRestore saveScope(m_scope, m_info.scopes.at(&_block).get()); + for (auto const& statement: _block.statements) + std::visit(*this, statement); +} + +std::pair ControlFlowGraphBuilder::makeConditionalJump(StackSlot _condition) +{ + CFG::BasicBlock& nonZero = m_graph.makeBlock(); + CFG::BasicBlock& zero = m_graph.makeBlock(); + makeConditionalJump(move(_condition), nonZero, zero); + return {&nonZero, &zero}; +} + +void ControlFlowGraphBuilder::makeConditionalJump(StackSlot _condition, CFG::BasicBlock& _nonZero, CFG::BasicBlock& _zero) +{ + yulAssert(m_currentBlock, ""); + m_currentBlock->exit = CFG::BasicBlock::ConditionalJump{ + move(_condition), + &_nonZero, + &_zero + }; + _nonZero.entries.emplace_back(m_currentBlock); + _zero.entries.emplace_back(m_currentBlock); + m_currentBlock = nullptr; +} + +void ControlFlowGraphBuilder::jump(CFG::BasicBlock& _target, bool backwards) +{ + yulAssert(m_currentBlock, ""); + m_currentBlock->exit = CFG::BasicBlock::Jump{&_target, backwards}; + _target.entries.emplace_back(m_currentBlock); + m_currentBlock = &_target; +} + +void ControlFlowGraphBuilder::operator()(If const& _if) +{ + auto&& [ifBranch, afterIf] = makeConditionalJump(std::visit(*this, *_if.condition)); + m_currentBlock = ifBranch; + (*this)(_if.body); + jump(*afterIf); +} + +void ControlFlowGraphBuilder::operator()(Switch const& _switch) +{ + yulAssert(m_currentBlock, ""); + auto ghostVariableId = m_graph.ghostVariables.size(); + YulString ghostVariableName("GHOST[" + to_string(ghostVariableId) + "]"); + auto& ghostVar = m_graph.ghostVariables.emplace_back(Scope::Variable{""_yulstring, ghostVariableName}); + + // Artificially generate: + // let := + VariableSlot ghostVarSlot{ghostVar, debugDataOf(*_switch.expression)}; + m_currentBlock->operations.emplace_back(CFG::Operation{ + Stack{std::visit(*this, *_switch.expression)}, + Stack{ghostVarSlot}, + CFG::Assignment{_switch.debugData, {ghostVarSlot}} + }); + + BuiltinFunction const* equalityBuiltin = m_dialect.equalityFunction({}); + yulAssert(equalityBuiltin, ""); + + // Artificially generate: + // eq(, ) + auto makeValueCompare = [&](Literal const& _value) { + yul::FunctionCall const& ghostCall = m_graph.ghostCalls.emplace_back(yul::FunctionCall{ + _value.debugData, + yul::Identifier{{}, "eq"_yulstring}, + {_value, Identifier{{}, ghostVariableName}} + }); + CFG::Operation& operation = m_currentBlock->operations.emplace_back(CFG::Operation{ + Stack{ghostVarSlot, LiteralSlot{valueOfLiteral(_value), _value.debugData}}, + Stack{TemporarySlot{ghostCall, 0}}, + CFG::BuiltinCall{_switch.debugData, *equalityBuiltin, ghostCall, 2}, + }); + return operation.output.front(); + }; + CFG::BasicBlock& afterSwitch = m_graph.makeBlock(); + for (auto const& switchCase: _switch.cases | ranges::views::drop_last(1)) + { + yulAssert(switchCase.value, ""); + auto&& [caseBranch, elseBranch] = makeConditionalJump(makeValueCompare(*switchCase.value)); + m_currentBlock = caseBranch; + (*this)(switchCase.body); + jump(afterSwitch); + m_currentBlock = elseBranch; + } + Case const& switchCase = _switch.cases.back(); + if (switchCase.value) + { + CFG::BasicBlock& caseBranch = m_graph.makeBlock(); + makeConditionalJump(makeValueCompare(*switchCase.value), caseBranch, afterSwitch); + m_currentBlock = &caseBranch; + (*this)(switchCase.body); + } + else + (*this)(switchCase.body); + jump(afterSwitch); +} + +void ControlFlowGraphBuilder::operator()(ForLoop const& _loop) +{ + ScopedSaveAndRestore scopeRestore(m_scope, m_info.scopes.at(&_loop.pre).get()); + (*this)(_loop.pre); + + std::optional constantCondition; + if (auto const* literalCondition = get_if(_loop.condition.get())) + { + if (valueOfLiteral(*literalCondition) == 0) + constantCondition = false; + else + constantCondition = true; + } + + CFG::BasicBlock& loopCondition = m_graph.makeBlock(); + CFG::BasicBlock& loopBody = m_graph.makeBlock(); + CFG::BasicBlock& post = m_graph.makeBlock(); + CFG::BasicBlock& afterLoop = m_graph.makeBlock(); + + ScopedSaveAndRestore scopedSaveAndRestore(m_forLoopInfo, ForLoopInfo{afterLoop, post}); + + if (constantCondition.has_value()) + { + if (*constantCondition) + { + jump(loopBody); + (*this)(_loop.body); + jump(post); + (*this)(_loop.post); + jump(loopBody, true); + } + else + jump(afterLoop); + } + else + { + jump(loopCondition); + makeConditionalJump(std::visit(*this, *_loop.condition), loopBody, afterLoop); + m_currentBlock = &loopBody; + (*this)(_loop.body); + jump(post); + (*this)(_loop.post); + jump(loopCondition, true); + } + + m_currentBlock = &afterLoop; +} + +void ControlFlowGraphBuilder::operator()(Break const&) +{ + yulAssert(m_forLoopInfo.has_value(), ""); + jump(m_forLoopInfo->afterLoop); + m_currentBlock = &m_graph.makeBlock(); +} + +void ControlFlowGraphBuilder::operator()(Continue const&) +{ + yulAssert(m_forLoopInfo.has_value(), ""); + jump(m_forLoopInfo->post); + m_currentBlock = &m_graph.makeBlock(); +} + +void ControlFlowGraphBuilder::operator()(Leave const&) +{ + yulAssert(m_currentFunctionExit.has_value(), ""); + m_currentBlock->exit = *m_currentFunctionExit; + m_currentBlock = &m_graph.makeBlock(); +} + +void ControlFlowGraphBuilder::operator()(FunctionDefinition const& _function) +{ + yulAssert(m_scope, ""); + yulAssert(m_scope->identifiers.count(_function.name), ""); + Scope::Function& function = std::get(m_scope->identifiers.at(_function.name)); + m_graph.functions.emplace_back(&function); + + yulAssert(m_info.scopes.at(&_function.body), ""); + Scope* virtualFunctionScope = m_info.scopes.at(m_info.virtualBlocks.at(&_function).get()).get(); + yulAssert(virtualFunctionScope, ""); + + auto&& [it, inserted] = m_graph.functionInfo.emplace(std::make_pair(&function, CFG::FunctionInfo{ + _function.debugData, + function, + &m_graph.makeBlock(), + _function.parameters | ranges::views::transform([&](auto const& _param) { + return VariableSlot{ + std::get(virtualFunctionScope->identifiers.at(_param.name)), + _param.debugData + }; + }) | ranges::to, + _function.returnVariables | ranges::views::transform([&](auto const& _retVar) { + return VariableSlot{ + std::get(virtualFunctionScope->identifiers.at(_retVar.name)), + _retVar.debugData + }; + }) | ranges::to + })); + yulAssert(inserted, ""); + CFG::FunctionInfo& info = it->second; + + ControlFlowGraphBuilder builder{m_graph, m_info, m_dialect}; + builder.m_currentFunctionExit = CFG::BasicBlock::FunctionReturn{&info}; + builder.m_currentBlock = info.entry; + builder(_function.body); + builder.m_currentBlock->exit = CFG::BasicBlock::FunctionReturn{&info}; +} + +Scope::Variable const& ControlFlowGraphBuilder::lookupVariable(YulString _name) const +{ + yulAssert(m_scope, ""); + Scope::Variable *var = nullptr; + if (m_scope->lookup(_name, util::GenericVisitor{ + [&](Scope::Variable& _var) { var = &_var; }, + [](Scope::Function&) + { + yulAssert(false, "Function not removed during desugaring."); + } + })) + { + yulAssert(var, ""); + return *var; + }; + yulAssert(false, "External identifier access unimplemented."); +} diff --git a/libyul/backends/evm/ControlFlowGraphBuilder.h b/libyul/backends/evm/ControlFlowGraphBuilder.h new file mode 100644 index 000000000..7e969e7be --- /dev/null +++ b/libyul/backends/evm/ControlFlowGraphBuilder.h @@ -0,0 +1,80 @@ +/* + 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 +/** + * Transformation of a Yul AST into a control flow graph. + */ +#pragma once + +#include + +namespace solidity::yul +{ + +class ControlFlowGraphBuilder +{ +public: + ControlFlowGraphBuilder(ControlFlowGraphBuilder const&) = delete; + ControlFlowGraphBuilder& operator=(ControlFlowGraphBuilder const&) = delete; + static std::unique_ptr build(AsmAnalysisInfo& _analysisInfo, Dialect const& _dialect, Block const& _block); + + StackSlot operator()(Expression const& _literal); + StackSlot operator()(Literal const& _literal); + StackSlot operator()(Identifier const& _identifier); + StackSlot operator()(FunctionCall const&); + + void operator()(VariableDeclaration const& _varDecl); + void operator()(Assignment const& _assignment); + void operator()(ExpressionStatement const& _statement); + + void operator()(Block const& _block); + + void operator()(If const& _if); + void operator()(Switch const& _switch); + void operator()(ForLoop const&); + void operator()(Break const&); + void operator()(Continue const&); + void operator()(Leave const&); + void operator()(FunctionDefinition const&); + +private: + ControlFlowGraphBuilder( + CFG& _graph, + AsmAnalysisInfo& _analysisInfo, + Dialect const& _dialect + ); + CFG::Operation& visitFunctionCall(FunctionCall const&); + + Scope::Variable const& lookupVariable(YulString _name) const; + std::pair makeConditionalJump(StackSlot _condition); + void makeConditionalJump(StackSlot _condition, CFG::BasicBlock& _nonZero, CFG::BasicBlock& _zero); + void jump(CFG::BasicBlock& _target, bool _backwards = false); + CFG& m_graph; + AsmAnalysisInfo& m_info; + Dialect const& m_dialect; + CFG::BasicBlock* m_currentBlock = nullptr; + Scope* m_scope = nullptr; + struct ForLoopInfo + { + std::reference_wrapper afterLoop; + std::reference_wrapper post; + }; + std::optional m_forLoopInfo; + std::optional m_currentFunctionExit; +}; + +}