From f3707f2ab0f856f0101dda87eca6996f21d286af 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 | 7 +- libyul/backends/evm/ControlFlowGraph.h | 210 ++++++++ .../backends/evm/ControlFlowGraphBuilder.cpp | 470 ++++++++++++++++++ libyul/backends/evm/ControlFlowGraphBuilder.h | 86 ++++ test/CMakeLists.txt | 2 + test/InteractiveTests.h | 30 +- test/libyul/ControlFlowGraphTest.cpp | 273 ++++++++++ test/libyul/ControlFlowGraphTest.h | 43 ++ .../yulControlFlowGraph/ambiguous_names.yul | 65 +++ test/libyul/yulControlFlowGraph/break.yul | 87 ++++ test/libyul/yulControlFlowGraph/complex.yul | 195 ++++++++ test/libyul/yulControlFlowGraph/for.yul | 51 ++ test/libyul/yulControlFlowGraph/function.yul | 75 +++ test/libyul/yulControlFlowGraph/if.yul | 37 ++ test/libyul/yulControlFlowGraph/leave.yul | 51 ++ .../nested_loop_complex.yul | 193 +++++++ test/libyul/yulControlFlowGraph/stub.yul | 15 + test/libyul/yulControlFlowGraph/switch.yul | 68 +++ test/libyul/yulControlFlowGraph/variables.yul | 31 ++ test/libyul/yulControlFlowGraph/verbatim.yul | 26 + test/tools/CMakeLists.txt | 4 +- 24 files changed, 2019 insertions(+), 17 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 create mode 100644 test/libyul/ControlFlowGraphTest.cpp create mode 100644 test/libyul/ControlFlowGraphTest.h create mode 100644 test/libyul/yulControlFlowGraph/ambiguous_names.yul create mode 100644 test/libyul/yulControlFlowGraph/break.yul create mode 100644 test/libyul/yulControlFlowGraph/complex.yul create mode 100644 test/libyul/yulControlFlowGraph/for.yul create mode 100644 test/libyul/yulControlFlowGraph/function.yul create mode 100644 test/libyul/yulControlFlowGraph/if.yul create mode 100644 test/libyul/yulControlFlowGraph/leave.yul create mode 100644 test/libyul/yulControlFlowGraph/nested_loop_complex.yul create mode 100644 test/libyul/yulControlFlowGraph/stub.yul create mode 100644 test/libyul/yulControlFlowGraph/switch.yul create mode 100644 test/libyul/yulControlFlowGraph/variables.yul create mode 100644 test/libyul/yulControlFlowGraph/verbatim.yul 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..f9ac3830a 100644 --- a/libyul/Scope.h +++ b/libyul/Scope.h @@ -37,11 +37,16 @@ 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..60bec498b --- /dev/null +++ b/libyul/backends/evm/ControlFlowGraph.h @@ -0,0 +1,210 @@ +/* + 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 following structs describe different kinds of stack slots. +/// Each stack slot is equality- and less-than-comparable and +/// specifies an attribute ``canBeFreelyGenerated`` that is true, +/// if a slot of this kind always has a known value at compile time and +/// therefore can safely be removed from the stack at any time and then +/// regenerated later. + +/// 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 jump target of a function while generating the code of the function body. +/// I.e. the caller of a function pushes a ``FunctionCallReturnLabelSlot`` (see above) before jumping to the function and +/// this very slot is viewed as ``FunctionReturnLabelSlot`` inside the function body and jumped to when returning from +/// the function. +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 index-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 ``CFG::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; + ~CFG() = default; + + 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. + /// Literal arguments (like the literal string in ``datasize``) do not have a location on the stack, + /// but are handled internally by the builtin's code generation function. + 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 + { + 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::vector entries; + std::vector operations; + 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 generated variables for explicit ownership. + /// Ghost variables are generated to store switch conditions when transforming the control flow + /// of a switch to a sequence of conditional jumps. + std::list ghostVariables; + /// Container for generated calls for explicit ownership. + /// Ghost calls are used for the equality comparisons of the switch condition ghost variable with + /// the switch case literals when transforming the control flow of a switch to a sequence of conditional jumps. + 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..f3a1d5823 --- /dev/null +++ b/libyul/backends/evm/ControlFlowGraphBuilder.cpp @@ -0,0 +1,470 @@ +/* + 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 const& _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}}; + for (auto const& functionInfo: result->functionInfo | ranges::views::values) + reachabilityCheck.verticesToTraverse.emplace_back(functionInfo.entry); + + reachabilityCheck.run([&](CFG::BasicBlock* _node, auto&& _addChild) { + visit(util::GenericVisitor{ + [&](CFG::BasicBlock::Jump const& _jump) { + _addChild(_jump.target); + }, + [&](CFG::BasicBlock::ConditionalJump const& _jump) { + _addChild(_jump.zero); + _addChild(_jump.nonZero); + }, + [](CFG::BasicBlock::FunctionReturn const&) {}, + [](CFG::BasicBlock::Terminated const&) {}, + [](CFG::BasicBlock::MainExit const&) {} + }, _node->exit); + }); + + // Remove all entries from unreachable nodes from the graph. + for (CFG::BasicBlock* 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 const& _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); +} + +StackSlot ControlFlowGraphBuilder::operator()(FunctionCall const& _call) +{ + CFG::Operation const& 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 = visitAssignmentRightHandSide(*_varDecl.value, declaredVariables.size()); + else + input = Stack(_varDecl.variables.size(), LiteralSlot{0, _varDecl.debugData}); + m_currentBlock->operations.emplace_back(CFG::Operation{ + std::move(input), + declaredVariables | ranges::to, + CFG::Assignment{_varDecl.debugData, declaredVariables} + }); +} +void ControlFlowGraphBuilder::operator()(Assignment const& _assignment) +{ + auto 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 + visitAssignmentRightHandSide(*_assignment.value, assignedVariables.size()), + // 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 const& 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); +} + +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(); + yulAssert(!_switch.cases.empty(), ""); + 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); + 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())) + constantCondition = valueOfLiteral(*literalCondition) != 0; + + 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& functionInfo = it->second; + + ControlFlowGraphBuilder builder{m_graph, m_info, m_dialect}; + builder.m_currentFunctionExit = CFG::BasicBlock::FunctionReturn{&functionInfo}; + builder.m_currentBlock = functionInfo.entry; + builder(_function.body); + builder.m_currentBlock->exit = CFG::BasicBlock::FunctionReturn{&functionInfo}; +} + + +CFG::Operation const& ControlFlowGraphBuilder::visitFunctionCall(FunctionCall const& _call) +{ + yulAssert(m_scope, ""); + yulAssert(m_currentBlock, ""); + + if (BuiltinFunction const* builtin = m_dialect.builtin(_call.functionName.name)) + { + Stack inputs; + for (auto&& [idx, arg]: _call.arguments | ranges::views::enumerate | ranges::views::reverse) + if (!builtin->literalArgument(idx).has_value()) + inputs.emplace_back(std::visit(*this, arg)); + CFG::BuiltinCall builtinCall{_call.debugData, *builtin, _call, inputs.size()}; + return m_currentBlock->operations.emplace_back(CFG::Operation{ + // input + std::move(inputs), + // output + ranges::views::iota(0u, builtin->returns.size()) | ranges::views::transform([&](size_t _i) { + return TemporarySlot{_call, _i}; + }) | ranges::to, + // operation + move(builtinCall) + }); + } + else + { + Scope::Function const& function = lookupFunction(_call.functionName.name); + Stack inputs{FunctionCallReturnLabelSlot{_call}}; + for (auto const& arg: _call.arguments | ranges::views::reverse) + inputs.emplace_back(std::visit(*this, arg)); + return m_currentBlock->operations.emplace_back(CFG::Operation{ + // input + std::move(inputs), + // 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} + }); + } +} + +Stack ControlFlowGraphBuilder::visitAssignmentRightHandSide(Expression const& _expression, size_t _expectedSlotCount) +{ + return std::visit(util::GenericVisitor{ + [&](FunctionCall const& _call) -> Stack { + CFG::Operation const& operation = visitFunctionCall(_call); + yulAssert(_expectedSlotCount == operation.output.size(), ""); + return operation.output; + }, + [&](auto const& _identifierOrLiteral) -> Stack { + yulAssert(_expectedSlotCount == 1, ""); + return {(*this)(_identifierOrLiteral)}; + } + }, _expression); +} + +Scope::Function const& ControlFlowGraphBuilder::lookupFunction(YulString _name) const +{ + Scope::Function const* function = nullptr; + yulAssert(m_scope->lookup(_name, util::GenericVisitor{ + [](Scope::Variable&) { yulAssert(false, "Expected function name."); }, + [&](Scope::Function& _function) { function = &_function; } + }), "Function name not found."); + yulAssert(function, ""); + return *function; +} + +Scope::Variable const& ControlFlowGraphBuilder::lookupVariable(YulString _name) const +{ + yulAssert(m_scope, ""); + Scope::Variable const* 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."); +} + +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; +} diff --git a/libyul/backends/evm/ControlFlowGraphBuilder.h b/libyul/backends/evm/ControlFlowGraphBuilder.h new file mode 100644 index 000000000..2a70a9472 --- /dev/null +++ b/libyul/backends/evm/ControlFlowGraphBuilder.h @@ -0,0 +1,86 @@ +/* + 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 const& _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 const& _analysisInfo, + Dialect const& _dialect + ); + CFG::Operation const& visitFunctionCall(FunctionCall const&); + Stack visitAssignmentRightHandSide(Expression const& _expression, size_t _expectedSlotCount); + + Scope::Function const& lookupFunction(YulString _name) const; + Scope::Variable const& lookupVariable(YulString _name) const; + /// @returns a pair of newly created blocks, the first element being the non-zero branch, the second element the + /// zero branch. + /// Resets m_currentBlock to enforce a subsequent explicit reassignment. + std::pair makeConditionalJump(StackSlot _condition); + /// Resets m_currentBlock to enforce a subsequent explicit reassignment. + void makeConditionalJump(StackSlot _condition, CFG::BasicBlock& _nonZero, CFG::BasicBlock& _zero); + void jump(CFG::BasicBlock& _target, bool _backwards = false); + CFG& m_graph; + AsmAnalysisInfo const& 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; +}; + +} diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index c82f1ee7b..17300c660 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -126,6 +126,8 @@ set(libyul_sources libyul/Common.cpp libyul/Common.h libyul/CompilabilityChecker.cpp + libyul/ControlFlowGraphTest.cpp + libyul/ControlFlowGraphTest.h libyul/EVMCodeTransformTest.cpp libyul/EVMCodeTransformTest.h libyul/EwasmTranslationTest.cpp diff --git a/test/InteractiveTests.h b/test/InteractiveTests.h index 68ca721a5..4ffdf5a74 100644 --- a/test/InteractiveTests.h +++ b/test/InteractiveTests.h @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -55,20 +56,21 @@ struct Testsuite Testsuite const g_interactiveTestsuites[] = { /* Title Path Subpath SMT NeedsVM Creator function */ - {"Ewasm Translation", "libyul", "ewasmTranslationTests",false,false, &yul::test::EwasmTranslationTest::create}, - {"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}, - {"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}, - {"Error Recovery", "libsolidity", "errorRecoveryTests", false, false, &SyntaxTest::createErrorRecovery}, - {"Semantic", "libsolidity", "semanticTests", false, true, &SemanticTest::create}, - {"JSON AST", "libsolidity", "ASTJSON", false, false, &ASTJSONTest::create}, - {"JSON ABI", "libsolidity", "ABIJson", false, false, &ABIJsonTest::create}, - {"SMT Checker", "libsolidity", "smtCheckerTests", true, false, &SMTCheckerTest::create}, - {"Gas Estimates", "libsolidity", "gasTests", false, false, &GasTest::create} + {"Ewasm Translation", "libyul", "ewasmTranslationTests", false, false, &yul::test::EwasmTranslationTest::create}, + {"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}, + {"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}, + {"Error Recovery", "libsolidity", "errorRecoveryTests", false, false, &SyntaxTest::createErrorRecovery}, + {"Semantic", "libsolidity", "semanticTests", false, true, &SemanticTest::create}, + {"JSON AST", "libsolidity", "ASTJSON", false, false, &ASTJSONTest::create}, + {"JSON ABI", "libsolidity", "ABIJson", false, false, &ABIJsonTest::create}, + {"SMT Checker", "libsolidity", "smtCheckerTests", true, false, &SMTCheckerTest::create}, + {"Gas Estimates", "libsolidity", "gasTests", false, false, &GasTest::create} }; } diff --git a/test/libyul/ControlFlowGraphTest.cpp b/test/libyul/ControlFlowGraphTest.cpp new file mode 100644 index 000000000..1107f4ca2 --- /dev/null +++ b/test/libyul/ControlFlowGraphTest.cpp @@ -0,0 +1,273 @@ +/* + 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 + +#ifdef ISOLTEST +#include +#endif + +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; +using namespace std; + +ControlFlowGraphTest::ControlFlowGraphTest(string const& _filename): + TestCase(_filename) +{ + 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(); +} + +namespace +{ +static std::string stackSlotToString(StackSlot const& _slot) +{ + return std::visit(util::GenericVisitor{ + [](FunctionCallReturnLabelSlot const& _ret) -> std::string { return "RET[" + _ret.call.get().functionName.name.str() + "]"; }, + [](FunctionReturnLabelSlot const&) -> std::string { return "RET"; }, + [](VariableSlot const& _var) { return _var.variable.get().name.str(); }, + [](LiteralSlot const& _lit) { return util::toCompactHexWithPrefix(_lit.value); }, + [](TemporarySlot const& _tmp) -> std::string { return "TMP[" + _tmp.call.get().functionName.name.str() + ", " + std::to_string(_tmp.index) + "]"; }, + [](JunkSlot const&) -> std::string { return "JUNK"; } + }, _slot); +} + +static std::string stackToString(Stack const& _stack) +{ + std::string result("[ "); + for (auto const& slot: _stack) + result += stackSlotToString(slot) + ' '; + result += ']'; + return result; +} +static std::string variableSlotToString(VariableSlot const& _slot) +{ + return _slot.variable.get().name.str(); +} +} + +class ControlFlowGraphPrinter +{ +public: + ControlFlowGraphPrinter(std::ostream& _stream): + m_stream(_stream) + { + } + void operator()(CFG::BasicBlock const& _block, bool _isMainEntry = true) + { + if (_isMainEntry) + { + m_stream << "Entry [label=\"Entry\"];\n"; + m_stream << "Entry -> Block" << getBlockId(_block) << ";\n"; + } + while (!m_blocksToPrint.empty()) + { + CFG::BasicBlock const* block = *m_blocksToPrint.begin(); + m_blocksToPrint.erase(m_blocksToPrint.begin()); + printBlock(*block); + } + + } + void operator()( + CFG::FunctionInfo const& _info + ) + { + m_stream << "FunctionEntry_" << _info.function.name.str() << "_" << getBlockId(*_info.entry) << " [label=\""; + m_stream << "function " << _info.function.name.str() << "("; + m_stream << joinHumanReadable(_info.parameters | ranges::views::transform(variableSlotToString)); + m_stream << ")"; + if (!_info.returnVariables.empty()) + { + m_stream << " -> "; + m_stream << joinHumanReadable(_info.returnVariables | ranges::views::transform(variableSlotToString)); + } + m_stream << "\"];\n"; + m_stream << "FunctionEntry_" << _info.function.name.str() << "_" << getBlockId(*_info.entry) << " -> Block" << getBlockId(*_info.entry) << ";\n"; + (*this)(*_info.entry, false); + } + +private: + void printBlock(CFG::BasicBlock const& _block) + { + m_stream << "Block" << getBlockId(_block) << " [label=\"\\\n"; + + // Verify that the entries of this block exit into this block. + for (auto const& entry: _block.entries) + std::visit(util::GenericVisitor{ + [&](CFG::BasicBlock::Jump const& _jump) + { + soltestAssert(_jump.target == &_block, "Invalid control flow graph."); + }, + [&](CFG::BasicBlock::ConditionalJump const& _conditionalJump) + { + soltestAssert( + _conditionalJump.zero == &_block || _conditionalJump.nonZero == &_block, + "Invalid control flow graph." + ); + }, + [&](auto const&) + { + soltestAssert(false, "Invalid control flow graph."); + } + }, entry->exit); + + for (auto const& operation: _block.operations) + { + std::visit(util::GenericVisitor{ + [&](CFG::FunctionCall const& _call) { + m_stream << _call.function.get().name.str() << ": "; + }, + [&](CFG::BuiltinCall const& _call) { + m_stream << _call.functionCall.get().functionName.name.str() << ": "; + + }, + [&](CFG::Assignment const& _assignment) { + m_stream << "Assignment("; + m_stream << joinHumanReadable(_assignment.variables | ranges::views::transform(variableSlotToString)); + m_stream << "): "; + } + }, operation.operation); + m_stream << stackToString(operation.input) << " => " << stackToString(operation.output) << "\\l\\\n"; + } + m_stream << "\"];\n"; + std::visit(util::GenericVisitor{ + [&](CFG::BasicBlock::MainExit const&) + { + m_stream << "Block" << getBlockId(_block) << "Exit [label=\"MainExit\"];\n"; + m_stream << "Block" << getBlockId(_block) << " -> Block" << getBlockId(_block) << "Exit;\n"; + }, + [&](CFG::BasicBlock::Jump const& _jump) + { + m_stream << "Block" << getBlockId(_block) << " -> Block" << getBlockId(_block) << "Exit [arrowhead=none];\n"; + m_stream << "Block" << getBlockId(_block) << "Exit [label=\""; + if (_jump.backwards) + m_stream << "Backwards"; + m_stream << "Jump\" shape=oval];\n"; + m_stream << "Block" << getBlockId(_block) << "Exit -> Block" << getBlockId(*_jump.target) << ";\n"; + }, + [&](CFG::BasicBlock::ConditionalJump const& _conditionalJump) + { + m_stream << "Block" << getBlockId(_block) << " -> Block" << getBlockId(_block) << "Exit;\n"; + m_stream << "Block" << getBlockId(_block) << "Exit [label=\"{ "; + m_stream << stackSlotToString(_conditionalJump.condition); + m_stream << "| { <0> Zero | <1> NonZero }}\" shape=Mrecord];\n"; + m_stream << "Block" << getBlockId(_block); + m_stream << "Exit:0 -> Block" << getBlockId(*_conditionalJump.zero) << ";\n"; + m_stream << "Block" << getBlockId(_block); + m_stream << "Exit:1 -> Block" << getBlockId(*_conditionalJump.nonZero) << ";\n"; + }, + [&](CFG::BasicBlock::FunctionReturn const& _return) + { + m_stream << "Block" << getBlockId(_block) << "Exit [label=\"FunctionReturn[" << _return.info->function.name.str() << "]\"];\n"; + m_stream << "Block" << getBlockId(_block) << " -> Block" << getBlockId(_block) << "Exit;\n"; + }, + [&](CFG::BasicBlock::Terminated const&) + { + m_stream << "Block" << getBlockId(_block) << "Exit [label=\"Terminated\"];\n"; + m_stream << "Block" << getBlockId(_block) << " -> Block" << getBlockId(_block) << "Exit;\n"; + } + }, _block.exit); + m_stream << "\n"; + } + size_t getBlockId(CFG::BasicBlock const& _block) + { + if (size_t* id = util::valueOrNullptr(m_blockIds, &_block)) + return *id; + size_t id = m_blockIds[&_block] = m_blockCount++; + m_blocksToPrint.emplace_back(&_block); + return id; + } + std::ostream& m_stream; + std::map m_blockIds; + size_t m_blockCount = 0; + std::list m_blocksToPrint; +}; + +TestCase::TestResult ControlFlowGraphTest::run(ostream& _stream, string const& _linePrefix, bool const _formatted) +{ + ErrorList errors; + auto [object, analysisInfo] = parse(m_source, *m_dialect, errors); + if (!object || !analysisInfo || !Error::containsOnlyWarnings(errors)) + { + AnsiColorized(_stream, _formatted, {formatting::BOLD, formatting::RED}) << _linePrefix << "Error parsing source." << endl; + return TestResult::FatalError; + } + + std::ostringstream output; + + std::unique_ptr cfg = ControlFlowGraphBuilder::build(*analysisInfo, *m_dialect, *object->code); + + output << "digraph CFG {\nnodesep=0.7;\nnode[shape=box];\n\n"; + ControlFlowGraphPrinter printer{output}; + printer(*cfg->entry); + for (auto function: cfg->functions) + printer(cfg->functionInfo.at(function)); + output << "}\n"; + + m_obtainedResult = output.str(); + + auto result = checkResult(_stream, _linePrefix, _formatted); + +#ifdef ISOLTEST + char* graphDisplayer = nullptr; + // The environment variables specify an optional command that will receive the graph encoded in DOT through stdin. + // Examples for suitable commands are ``dot -Tx11:cairo`` or ``xdot -``. + if (result == TestResult::Failure) + // ISOLTEST_DISPLAY_GRAPHS_ON_FAILURE_COMMAND will run on all failing tests (intended for use during modifications). + graphDisplayer = getenv("ISOLTEST_DISPLAY_GRAPHS_ON_FAILURE_COMMAND"); + else if (result == TestResult::Success) + // ISOLTEST_DISPLAY_GRAPHS_ON_FAILURE_COMMAND will run on all succeeding tests (intended for use during reviews). + graphDisplayer = getenv("ISOLTEST_DISPLAY_GRAPHS_ON_SUCCESS_COMMAND"); + + if (graphDisplayer) + { + if (result == TestResult::Success) + std::cout << std::endl << m_source << std::endl; + boost::process::opstream pipe; + boost::process::child child(graphDisplayer, boost::process::std_in < pipe); + + pipe << output.str(); + pipe.flush(); + pipe.pipe().close(); + if (result == TestResult::Success) + child.wait(); + else + child.detach(); + } +#endif + + return result; + +} diff --git a/test/libyul/ControlFlowGraphTest.h b/test/libyul/ControlFlowGraphTest.h new file mode 100644 index 000000000..44cea06d3 --- /dev/null +++ b/test/libyul/ControlFlowGraphTest.h @@ -0,0 +1,43 @@ +/* + 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 +{ +struct Dialect; + +namespace test +{ + +class ControlFlowGraphTest: public solidity::frontend::test::TestCase +{ +public: + static std::unique_ptr create(Config const& _config) + { + return std::make_unique(_config.filename); + } + explicit ControlFlowGraphTest(std::string const& _filename); + TestResult run(std::ostream& _stream, std::string const& _linePrefix = "", bool const _formatted = false) override; +private: + Dialect const* m_dialect = nullptr; +}; +} +} diff --git a/test/libyul/yulControlFlowGraph/ambiguous_names.yul b/test/libyul/yulControlFlowGraph/ambiguous_names.yul new file mode 100644 index 000000000..fabeda45b --- /dev/null +++ b/test/libyul/yulControlFlowGraph/ambiguous_names.yul @@ -0,0 +1,65 @@ +{ + a() + c() + function a() { + let x := 42 + sstore(x,x) + b() + function b() {} + } + function c() { + let x := 21 + mstore(x,x) + b() + function b() {} + } +} +// ---- +// digraph CFG { +// nodesep=0.7; +// node[shape=box]; +// +// Entry [label="Entry"]; +// Entry -> Block0; +// Block0 [label="\ +// a: [ RET[a] ] => [ ]\l\ +// c: [ RET[c] ] => [ ]\l\ +// "]; +// Block0Exit [label="MainExit"]; +// Block0 -> Block0Exit; +// +// FunctionEntry_a_1 [label="function a()"]; +// FunctionEntry_a_1 -> Block1; +// Block1 [label="\ +// Assignment(x): [ 0x2a ] => [ x ]\l\ +// sstore: [ x x ] => [ ]\l\ +// b: [ RET[b] ] => [ ]\l\ +// "]; +// Block1Exit [label="FunctionReturn[a]"]; +// Block1 -> Block1Exit; +// +// FunctionEntry_b_2 [label="function b()"]; +// FunctionEntry_b_2 -> Block2; +// Block2 [label="\ +// "]; +// Block2Exit [label="FunctionReturn[b]"]; +// Block2 -> Block2Exit; +// +// FunctionEntry_c_3 [label="function c()"]; +// FunctionEntry_c_3 -> Block3; +// Block3 [label="\ +// Assignment(x): [ 0x15 ] => [ x ]\l\ +// mstore: [ x x ] => [ ]\l\ +// b: [ RET[b] ] => [ ]\l\ +// "]; +// Block3Exit [label="FunctionReturn[c]"]; +// Block3 -> Block3Exit; +// +// FunctionEntry_b_4 [label="function b()"]; +// FunctionEntry_b_4 -> Block4; +// Block4 [label="\ +// "]; +// Block4Exit [label="FunctionReturn[b]"]; +// Block4 -> Block4Exit; +// +// } diff --git a/test/libyul/yulControlFlowGraph/break.yul b/test/libyul/yulControlFlowGraph/break.yul new file mode 100644 index 000000000..ceca7cce0 --- /dev/null +++ b/test/libyul/yulControlFlowGraph/break.yul @@ -0,0 +1,87 @@ +{ + sstore(0x01, 0x0101) + for { sstore(0x02, 0x0202) } sload(0x03) { sstore(0x04, 0x0404) } { + sstore(0x05, 0x0505) + if sload(0x06) { sstore(0x07,0x0707) break } + sstore(0x08, 0x0808) + if sload(0x09) { sstore(0x0A,0x0A0A) continue } + sstore(0x0B, 0x0B0B) + } + sstore(0x0C, 0x0C0C) +} +// ---- +// digraph CFG { +// nodesep=0.7; +// node[shape=box]; +// +// Entry [label="Entry"]; +// Entry -> Block0; +// Block0 [label="\ +// sstore: [ 0x0101 0x01 ] => [ ]\l\ +// sstore: [ 0x0202 0x02 ] => [ ]\l\ +// "]; +// Block0 -> Block0Exit [arrowhead=none]; +// Block0Exit [label="Jump" shape=oval]; +// Block0Exit -> Block1; +// +// Block1 [label="\ +// sload: [ 0x03 ] => [ TMP[sload, 0] ]\l\ +// "]; +// Block1 -> Block1Exit; +// Block1Exit [label="{ TMP[sload, 0]| { <0> Zero | <1> NonZero }}" shape=Mrecord]; +// Block1Exit:0 -> Block2; +// Block1Exit:1 -> Block3; +// +// Block2 [label="\ +// sstore: [ 0x0c0c 0x0c ] => [ ]\l\ +// "]; +// Block2Exit [label="MainExit"]; +// Block2 -> Block2Exit; +// +// Block3 [label="\ +// sstore: [ 0x0505 0x05 ] => [ ]\l\ +// sload: [ 0x06 ] => [ TMP[sload, 0] ]\l\ +// "]; +// Block3 -> Block3Exit; +// Block3Exit [label="{ TMP[sload, 0]| { <0> Zero | <1> NonZero }}" shape=Mrecord]; +// Block3Exit:0 -> Block4; +// Block3Exit:1 -> Block5; +// +// Block4 [label="\ +// sstore: [ 0x0808 0x08 ] => [ ]\l\ +// sload: [ 0x09 ] => [ TMP[sload, 0] ]\l\ +// "]; +// Block4 -> Block4Exit; +// Block4Exit [label="{ TMP[sload, 0]| { <0> Zero | <1> NonZero }}" shape=Mrecord]; +// Block4Exit:0 -> Block6; +// Block4Exit:1 -> Block7; +// +// Block5 [label="\ +// sstore: [ 0x0707 0x07 ] => [ ]\l\ +// "]; +// Block5 -> Block5Exit [arrowhead=none]; +// Block5Exit [label="Jump" shape=oval]; +// Block5Exit -> Block2; +// +// Block6 [label="\ +// sstore: [ 0x0b0b 0x0b ] => [ ]\l\ +// "]; +// Block6 -> Block6Exit [arrowhead=none]; +// Block6Exit [label="Jump" shape=oval]; +// Block6Exit -> Block8; +// +// Block7 [label="\ +// sstore: [ 0x0a0a 0x0a ] => [ ]\l\ +// "]; +// Block7 -> Block7Exit [arrowhead=none]; +// Block7Exit [label="Jump" shape=oval]; +// Block7Exit -> Block8; +// +// Block8 [label="\ +// sstore: [ 0x0404 0x04 ] => [ ]\l\ +// "]; +// Block8 -> Block8Exit [arrowhead=none]; +// Block8Exit [label="BackwardsJump" shape=oval]; +// Block8Exit -> Block1; +// +// } diff --git a/test/libyul/yulControlFlowGraph/complex.yul b/test/libyul/yulControlFlowGraph/complex.yul new file mode 100644 index 000000000..4001a9309 --- /dev/null +++ b/test/libyul/yulControlFlowGraph/complex.yul @@ -0,0 +1,195 @@ +{ + function f(a, b) -> c { + for { let x := 42 } lt(x, a) { + x := add(x, 1) + if calldataload(x) + { + sstore(0, x) + leave + sstore(0x01, 0x0101) + } + sstore(0xFF, 0xFFFF) + } + { + switch mload(x) + case 0 { + sstore(0x02, 0x0202) + break + sstore(0x03, 0x0303) + } + case 1 { + sstore(0x04, 0x0404) + leave + sstore(0x05, 0x0505) + } + case 2 { + sstore(0x06, 0x0606) + revert(0, 0) + sstore(0x07, 0x0707) + } + case 3 { + sstore(0x08, 0x0808) + } + default { + if mload(b) { + return(0, 0) + sstore(0x09, 0x0909) + } + sstore(0x0A, 0x0A0A) + } + sstore(0x0B, 0x0B0B) + } + sstore(0x0C, 0x0C0C) + } + pop(f(1,2)) +} +// ---- +// digraph CFG { +// nodesep=0.7; +// node[shape=box]; +// +// Entry [label="Entry"]; +// Entry -> Block0; +// Block0 [label="\ +// f: [ RET[f] 0x02 0x01 ] => [ TMP[f, 0] ]\l\ +// pop: [ TMP[f, 0] ] => [ ]\l\ +// "]; +// Block0Exit [label="MainExit"]; +// Block0 -> Block0Exit; +// +// FunctionEntry_f_1 [label="function f(a, b) -> c"]; +// FunctionEntry_f_1 -> Block1; +// Block1 [label="\ +// Assignment(x): [ 0x2a ] => [ x ]\l\ +// "]; +// Block1 -> Block1Exit [arrowhead=none]; +// Block1Exit [label="Jump" shape=oval]; +// Block1Exit -> Block2; +// +// Block2 [label="\ +// lt: [ a x ] => [ TMP[lt, 0] ]\l\ +// "]; +// Block2 -> Block2Exit; +// Block2Exit [label="{ TMP[lt, 0]| { <0> Zero | <1> NonZero }}" shape=Mrecord]; +// Block2Exit:0 -> Block3; +// Block2Exit:1 -> Block4; +// +// Block3 [label="\ +// sstore: [ 0x0c0c 0x0c ] => [ ]\l\ +// "]; +// Block3Exit [label="FunctionReturn[f]"]; +// Block3 -> Block3Exit; +// +// Block4 [label="\ +// mload: [ x ] => [ TMP[mload, 0] ]\l\ +// Assignment(GHOST[0]): [ TMP[mload, 0] ] => [ GHOST[0] ]\l\ +// eq: [ GHOST[0] 0x00 ] => [ TMP[eq, 0] ]\l\ +// "]; +// Block4 -> Block4Exit; +// Block4Exit [label="{ TMP[eq, 0]| { <0> Zero | <1> NonZero }}" shape=Mrecord]; +// Block4Exit:0 -> Block5; +// Block4Exit:1 -> Block6; +// +// Block5 [label="\ +// eq: [ GHOST[0] 0x01 ] => [ TMP[eq, 0] ]\l\ +// "]; +// Block5 -> Block5Exit; +// Block5Exit [label="{ TMP[eq, 0]| { <0> Zero | <1> NonZero }}" shape=Mrecord]; +// Block5Exit:0 -> Block7; +// Block5Exit:1 -> Block8; +// +// Block6 [label="\ +// sstore: [ 0x0202 0x02 ] => [ ]\l\ +// "]; +// Block6 -> Block6Exit [arrowhead=none]; +// Block6Exit [label="Jump" shape=oval]; +// Block6Exit -> Block3; +// +// Block7 [label="\ +// eq: [ GHOST[0] 0x02 ] => [ TMP[eq, 0] ]\l\ +// "]; +// Block7 -> Block7Exit; +// Block7Exit [label="{ TMP[eq, 0]| { <0> Zero | <1> NonZero }}" shape=Mrecord]; +// Block7Exit:0 -> Block9; +// Block7Exit:1 -> Block10; +// +// Block8 [label="\ +// sstore: [ 0x0404 0x04 ] => [ ]\l\ +// "]; +// Block8Exit [label="FunctionReturn[f]"]; +// Block8 -> Block8Exit; +// +// Block9 [label="\ +// eq: [ GHOST[0] 0x03 ] => [ TMP[eq, 0] ]\l\ +// "]; +// Block9 -> Block9Exit; +// Block9Exit [label="{ TMP[eq, 0]| { <0> Zero | <1> NonZero }}" shape=Mrecord]; +// Block9Exit:0 -> Block11; +// Block9Exit:1 -> Block12; +// +// Block10 [label="\ +// sstore: [ 0x0606 0x06 ] => [ ]\l\ +// revert: [ 0x00 0x00 ] => [ ]\l\ +// "]; +// Block10Exit [label="Terminated"]; +// Block10 -> Block10Exit; +// +// Block11 [label="\ +// mload: [ b ] => [ TMP[mload, 0] ]\l\ +// "]; +// Block11 -> Block11Exit; +// Block11Exit [label="{ TMP[mload, 0]| { <0> Zero | <1> NonZero }}" shape=Mrecord]; +// Block11Exit:0 -> Block13; +// Block11Exit:1 -> Block14; +// +// Block12 [label="\ +// sstore: [ 0x0808 0x08 ] => [ ]\l\ +// "]; +// Block12 -> Block12Exit [arrowhead=none]; +// Block12Exit [label="Jump" shape=oval]; +// Block12Exit -> Block15; +// +// Block13 [label="\ +// sstore: [ 0x0a0a 0x0a ] => [ ]\l\ +// "]; +// Block13 -> Block13Exit [arrowhead=none]; +// Block13Exit [label="Jump" shape=oval]; +// Block13Exit -> Block15; +// +// Block14 [label="\ +// return: [ 0x00 0x00 ] => [ ]\l\ +// "]; +// Block14Exit [label="Terminated"]; +// Block14 -> Block14Exit; +// +// Block15 [label="\ +// sstore: [ 0x0b0b 0x0b ] => [ ]\l\ +// "]; +// Block15 -> Block15Exit [arrowhead=none]; +// Block15Exit [label="Jump" shape=oval]; +// Block15Exit -> Block16; +// +// Block16 [label="\ +// add: [ 0x01 x ] => [ TMP[add, 0] ]\l\ +// Assignment(x): [ TMP[add, 0] ] => [ x ]\l\ +// calldataload: [ x ] => [ TMP[calldataload, 0] ]\l\ +// "]; +// Block16 -> Block16Exit; +// Block16Exit [label="{ TMP[calldataload, 0]| { <0> Zero | <1> NonZero }}" shape=Mrecord]; +// Block16Exit:0 -> Block17; +// Block16Exit:1 -> Block18; +// +// Block17 [label="\ +// sstore: [ 0xffff 0xff ] => [ ]\l\ +// "]; +// Block17 -> Block17Exit [arrowhead=none]; +// Block17Exit [label="BackwardsJump" shape=oval]; +// Block17Exit -> Block2; +// +// Block18 [label="\ +// sstore: [ x 0x00 ] => [ ]\l\ +// "]; +// Block18Exit [label="FunctionReturn[f]"]; +// Block18 -> Block18Exit; +// +// } diff --git a/test/libyul/yulControlFlowGraph/for.yul b/test/libyul/yulControlFlowGraph/for.yul new file mode 100644 index 000000000..52658a128 --- /dev/null +++ b/test/libyul/yulControlFlowGraph/for.yul @@ -0,0 +1,51 @@ +{ + sstore(0x01, 0x0101) + for { sstore(0x02, 0x0202) } sload(0x03) { sstore(0x04, 0x0404) } { + sstore(0x05, 0x0505) + } + sstore(0x06, 0x0506) +} +// ---- +// digraph CFG { +// nodesep=0.7; +// node[shape=box]; +// +// Entry [label="Entry"]; +// Entry -> Block0; +// Block0 [label="\ +// sstore: [ 0x0101 0x01 ] => [ ]\l\ +// sstore: [ 0x0202 0x02 ] => [ ]\l\ +// "]; +// Block0 -> Block0Exit [arrowhead=none]; +// Block0Exit [label="Jump" shape=oval]; +// Block0Exit -> Block1; +// +// Block1 [label="\ +// sload: [ 0x03 ] => [ TMP[sload, 0] ]\l\ +// "]; +// Block1 -> Block1Exit; +// Block1Exit [label="{ TMP[sload, 0]| { <0> Zero | <1> NonZero }}" shape=Mrecord]; +// Block1Exit:0 -> Block2; +// Block1Exit:1 -> Block3; +// +// Block2 [label="\ +// sstore: [ 0x0506 0x06 ] => [ ]\l\ +// "]; +// Block2Exit [label="MainExit"]; +// Block2 -> Block2Exit; +// +// Block3 [label="\ +// sstore: [ 0x0505 0x05 ] => [ ]\l\ +// "]; +// Block3 -> Block3Exit [arrowhead=none]; +// Block3Exit [label="Jump" shape=oval]; +// Block3Exit -> Block4; +// +// Block4 [label="\ +// sstore: [ 0x0404 0x04 ] => [ ]\l\ +// "]; +// Block4 -> Block4Exit [arrowhead=none]; +// Block4Exit [label="BackwardsJump" shape=oval]; +// Block4Exit -> Block1; +// +// } diff --git a/test/libyul/yulControlFlowGraph/function.yul b/test/libyul/yulControlFlowGraph/function.yul new file mode 100644 index 000000000..862b1b9e6 --- /dev/null +++ b/test/libyul/yulControlFlowGraph/function.yul @@ -0,0 +1,75 @@ +{ + function f(a, b) -> r { + let x := add(a,b) + r := sub(x,a) + } + function g() { + sstore(0x01, 0x0101) + } + function h(x) { + h(f(x, 0)) + g() + } + function i() -> v, w { + v := 0x0202 + w := 0x0303 + } + let x, y := i() + h(x) + h(y) +} +// ---- +// digraph CFG { +// nodesep=0.7; +// node[shape=box]; +// +// Entry [label="Entry"]; +// Entry -> Block0; +// Block0 [label="\ +// i: [ RET[i] ] => [ TMP[i, 0] TMP[i, 1] ]\l\ +// Assignment(x, y): [ TMP[i, 0] TMP[i, 1] ] => [ x y ]\l\ +// h: [ RET[h] x ] => [ ]\l\ +// h: [ RET[h] y ] => [ ]\l\ +// "]; +// Block0Exit [label="MainExit"]; +// Block0 -> Block0Exit; +// +// FunctionEntry_f_1 [label="function f(a, b) -> r"]; +// FunctionEntry_f_1 -> Block1; +// Block1 [label="\ +// add: [ b a ] => [ TMP[add, 0] ]\l\ +// Assignment(x): [ TMP[add, 0] ] => [ x ]\l\ +// sub: [ a x ] => [ TMP[sub, 0] ]\l\ +// Assignment(r): [ TMP[sub, 0] ] => [ r ]\l\ +// "]; +// Block1Exit [label="FunctionReturn[f]"]; +// Block1 -> Block1Exit; +// +// FunctionEntry_g_2 [label="function g()"]; +// FunctionEntry_g_2 -> Block2; +// Block2 [label="\ +// sstore: [ 0x0101 0x01 ] => [ ]\l\ +// "]; +// Block2Exit [label="FunctionReturn[g]"]; +// Block2 -> Block2Exit; +// +// FunctionEntry_h_3 [label="function h(x)"]; +// FunctionEntry_h_3 -> Block3; +// Block3 [label="\ +// f: [ RET[f] 0x00 x ] => [ TMP[f, 0] ]\l\ +// h: [ RET[h] TMP[f, 0] ] => [ ]\l\ +// g: [ RET[g] ] => [ ]\l\ +// "]; +// Block3Exit [label="FunctionReturn[h]"]; +// Block3 -> Block3Exit; +// +// FunctionEntry_i_4 [label="function i() -> v, w"]; +// FunctionEntry_i_4 -> Block4; +// Block4 [label="\ +// Assignment(v): [ 0x0202 ] => [ v ]\l\ +// Assignment(w): [ 0x0303 ] => [ w ]\l\ +// "]; +// Block4Exit [label="FunctionReturn[i]"]; +// Block4 -> Block4Exit; +// +// } diff --git a/test/libyul/yulControlFlowGraph/if.yul b/test/libyul/yulControlFlowGraph/if.yul new file mode 100644 index 000000000..a313c53ee --- /dev/null +++ b/test/libyul/yulControlFlowGraph/if.yul @@ -0,0 +1,37 @@ +{ + sstore(0x01, 0x0101) + if calldataload(0) { + sstore(0x02, 0x0202) + } + sstore(0x03, 0x003) +} +// ---- +// digraph CFG { +// nodesep=0.7; +// node[shape=box]; +// +// Entry [label="Entry"]; +// Entry -> Block0; +// Block0 [label="\ +// sstore: [ 0x0101 0x01 ] => [ ]\l\ +// calldataload: [ 0x00 ] => [ TMP[calldataload, 0] ]\l\ +// "]; +// Block0 -> Block0Exit; +// Block0Exit [label="{ TMP[calldataload, 0]| { <0> Zero | <1> NonZero }}" shape=Mrecord]; +// Block0Exit:0 -> Block1; +// Block0Exit:1 -> Block2; +// +// Block1 [label="\ +// sstore: [ 0x03 0x03 ] => [ ]\l\ +// "]; +// Block1Exit [label="MainExit"]; +// Block1 -> Block1Exit; +// +// Block2 [label="\ +// sstore: [ 0x0202 0x02 ] => [ ]\l\ +// "]; +// Block2 -> Block2Exit [arrowhead=none]; +// Block2Exit [label="Jump" shape=oval]; +// Block2Exit -> Block1; +// +// } diff --git a/test/libyul/yulControlFlowGraph/leave.yul b/test/libyul/yulControlFlowGraph/leave.yul new file mode 100644 index 000000000..4d0a63af4 --- /dev/null +++ b/test/libyul/yulControlFlowGraph/leave.yul @@ -0,0 +1,51 @@ +{ + function f(a, b) -> c { + sstore(0x01, 0x0101) + if lt(a,b) { + sstore(0x02, 0x0202) + leave + sstore(0x03, 0x0303) + } + sstore(0x04, 0x0404) + } + + pop(f(0,1)) +} +// ---- +// digraph CFG { +// nodesep=0.7; +// node[shape=box]; +// +// Entry [label="Entry"]; +// Entry -> Block0; +// Block0 [label="\ +// f: [ RET[f] 0x01 0x00 ] => [ TMP[f, 0] ]\l\ +// pop: [ TMP[f, 0] ] => [ ]\l\ +// "]; +// Block0Exit [label="MainExit"]; +// Block0 -> Block0Exit; +// +// FunctionEntry_f_1 [label="function f(a, b) -> c"]; +// FunctionEntry_f_1 -> Block1; +// Block1 [label="\ +// sstore: [ 0x0101 0x01 ] => [ ]\l\ +// lt: [ b a ] => [ TMP[lt, 0] ]\l\ +// "]; +// Block1 -> Block1Exit; +// Block1Exit [label="{ TMP[lt, 0]| { <0> Zero | <1> NonZero }}" shape=Mrecord]; +// Block1Exit:0 -> Block2; +// Block1Exit:1 -> Block3; +// +// Block2 [label="\ +// sstore: [ 0x0404 0x04 ] => [ ]\l\ +// "]; +// Block2Exit [label="FunctionReturn[f]"]; +// Block2 -> Block2Exit; +// +// Block3 [label="\ +// sstore: [ 0x0202 0x02 ] => [ ]\l\ +// "]; +// Block3Exit [label="FunctionReturn[f]"]; +// Block3 -> Block3Exit; +// +// } diff --git a/test/libyul/yulControlFlowGraph/nested_loop_complex.yul b/test/libyul/yulControlFlowGraph/nested_loop_complex.yul new file mode 100644 index 000000000..ea9aedf76 --- /dev/null +++ b/test/libyul/yulControlFlowGraph/nested_loop_complex.yul @@ -0,0 +1,193 @@ +{ + for { let x := 0 } lt(x, 0x0101) { + sstore(x, 0x0202) + for { let y := 0 } lt(y, 0x0303) { y := add(y, 0x0404) } { + sstore(y, 0x0505) + } + x := add(x, 0x0202) + } + { + sstore(0x0606, 0x0606) + if sload(0x0707) { continue } + sstore(0x0808, 0x0808) + if sload(0x0909) { break } + sstore(0x0A0A, 0x0B0B) + for { let z := 0 } lt(z, 0x0C0C) { z := add(z, 1) } { + sstore(0x0D0D, 0x0D0D) + if sload(0x0E0E) { + continue + } + sstore(0x0F0F, 0x0F0F) + if sload(0x1010) { + break + } + sstore(0x1111, 0x1111) + } + sstore(0x1212, 0x1212) + } +} +// ---- +// digraph CFG { +// nodesep=0.7; +// node[shape=box]; +// +// Entry [label="Entry"]; +// Entry -> Block0; +// Block0 [label="\ +// Assignment(x): [ 0x00 ] => [ x ]\l\ +// "]; +// Block0 -> Block0Exit [arrowhead=none]; +// Block0Exit [label="Jump" shape=oval]; +// Block0Exit -> Block1; +// +// Block1 [label="\ +// lt: [ 0x0101 x ] => [ TMP[lt, 0] ]\l\ +// "]; +// Block1 -> Block1Exit; +// Block1Exit [label="{ TMP[lt, 0]| { <0> Zero | <1> NonZero }}" shape=Mrecord]; +// Block1Exit:0 -> Block2; +// Block1Exit:1 -> Block3; +// +// Block2 [label="\ +// "]; +// Block2Exit [label="MainExit"]; +// Block2 -> Block2Exit; +// +// Block3 [label="\ +// sstore: [ 0x0606 0x0606 ] => [ ]\l\ +// sload: [ 0x0707 ] => [ TMP[sload, 0] ]\l\ +// "]; +// Block3 -> Block3Exit; +// Block3Exit [label="{ TMP[sload, 0]| { <0> Zero | <1> NonZero }}" shape=Mrecord]; +// Block3Exit:0 -> Block4; +// Block3Exit:1 -> Block5; +// +// Block4 [label="\ +// sstore: [ 0x0808 0x0808 ] => [ ]\l\ +// sload: [ 0x0909 ] => [ TMP[sload, 0] ]\l\ +// "]; +// Block4 -> Block4Exit; +// Block4Exit [label="{ TMP[sload, 0]| { <0> Zero | <1> NonZero }}" shape=Mrecord]; +// Block4Exit:0 -> Block6; +// Block4Exit:1 -> Block7; +// +// Block5 [label="\ +// "]; +// Block5 -> Block5Exit [arrowhead=none]; +// Block5Exit [label="Jump" shape=oval]; +// Block5Exit -> Block8; +// +// Block6 [label="\ +// sstore: [ 0x0b0b 0x0a0a ] => [ ]\l\ +// Assignment(z): [ 0x00 ] => [ z ]\l\ +// "]; +// Block6 -> Block6Exit [arrowhead=none]; +// Block6Exit [label="Jump" shape=oval]; +// Block6Exit -> Block9; +// +// Block7 [label="\ +// "]; +// Block7 -> Block7Exit [arrowhead=none]; +// Block7Exit [label="Jump" shape=oval]; +// Block7Exit -> Block2; +// +// Block8 [label="\ +// sstore: [ 0x0202 x ] => [ ]\l\ +// Assignment(y): [ 0x00 ] => [ y ]\l\ +// "]; +// Block8 -> Block8Exit [arrowhead=none]; +// Block8Exit [label="Jump" shape=oval]; +// Block8Exit -> Block10; +// +// Block9 [label="\ +// lt: [ 0x0c0c z ] => [ TMP[lt, 0] ]\l\ +// "]; +// Block9 -> Block9Exit; +// Block9Exit [label="{ TMP[lt, 0]| { <0> Zero | <1> NonZero }}" shape=Mrecord]; +// Block9Exit:0 -> Block11; +// Block9Exit:1 -> Block12; +// +// Block10 [label="\ +// lt: [ 0x0303 y ] => [ TMP[lt, 0] ]\l\ +// "]; +// Block10 -> Block10Exit; +// Block10Exit [label="{ TMP[lt, 0]| { <0> Zero | <1> NonZero }}" shape=Mrecord]; +// Block10Exit:0 -> Block13; +// Block10Exit:1 -> Block14; +// +// Block11 [label="\ +// sstore: [ 0x1212 0x1212 ] => [ ]\l\ +// "]; +// Block11 -> Block11Exit [arrowhead=none]; +// Block11Exit [label="Jump" shape=oval]; +// Block11Exit -> Block8; +// +// Block12 [label="\ +// sstore: [ 0x0d0d 0x0d0d ] => [ ]\l\ +// sload: [ 0x0e0e ] => [ TMP[sload, 0] ]\l\ +// "]; +// Block12 -> Block12Exit; +// Block12Exit [label="{ TMP[sload, 0]| { <0> Zero | <1> NonZero }}" shape=Mrecord]; +// Block12Exit:0 -> Block15; +// Block12Exit:1 -> Block16; +// +// Block13 [label="\ +// add: [ 0x0202 x ] => [ TMP[add, 0] ]\l\ +// Assignment(x): [ TMP[add, 0] ] => [ x ]\l\ +// "]; +// Block13 -> Block13Exit [arrowhead=none]; +// Block13Exit [label="BackwardsJump" shape=oval]; +// Block13Exit -> Block1; +// +// Block14 [label="\ +// sstore: [ 0x0505 y ] => [ ]\l\ +// "]; +// Block14 -> Block14Exit [arrowhead=none]; +// Block14Exit [label="Jump" shape=oval]; +// Block14Exit -> Block17; +// +// Block15 [label="\ +// sstore: [ 0x0f0f 0x0f0f ] => [ ]\l\ +// sload: [ 0x1010 ] => [ TMP[sload, 0] ]\l\ +// "]; +// Block15 -> Block15Exit; +// Block15Exit [label="{ TMP[sload, 0]| { <0> Zero | <1> NonZero }}" shape=Mrecord]; +// Block15Exit:0 -> Block18; +// Block15Exit:1 -> Block19; +// +// Block16 [label="\ +// "]; +// Block16 -> Block16Exit [arrowhead=none]; +// Block16Exit [label="Jump" shape=oval]; +// Block16Exit -> Block20; +// +// Block17 [label="\ +// add: [ 0x0404 y ] => [ TMP[add, 0] ]\l\ +// Assignment(y): [ TMP[add, 0] ] => [ y ]\l\ +// "]; +// Block17 -> Block17Exit [arrowhead=none]; +// Block17Exit [label="BackwardsJump" shape=oval]; +// Block17Exit -> Block10; +// +// Block18 [label="\ +// sstore: [ 0x1111 0x1111 ] => [ ]\l\ +// "]; +// Block18 -> Block18Exit [arrowhead=none]; +// Block18Exit [label="Jump" shape=oval]; +// Block18Exit -> Block20; +// +// Block19 [label="\ +// "]; +// Block19 -> Block19Exit [arrowhead=none]; +// Block19Exit [label="Jump" shape=oval]; +// Block19Exit -> Block11; +// +// Block20 [label="\ +// add: [ 0x01 z ] => [ TMP[add, 0] ]\l\ +// Assignment(z): [ TMP[add, 0] ] => [ z ]\l\ +// "]; +// Block20 -> Block20Exit [arrowhead=none]; +// Block20Exit [label="BackwardsJump" shape=oval]; +// Block20Exit -> Block9; +// +// } diff --git a/test/libyul/yulControlFlowGraph/stub.yul b/test/libyul/yulControlFlowGraph/stub.yul new file mode 100644 index 000000000..4ec901619 --- /dev/null +++ b/test/libyul/yulControlFlowGraph/stub.yul @@ -0,0 +1,15 @@ +{ +} +// ---- +// digraph CFG { +// nodesep=0.7; +// node[shape=box]; +// +// Entry [label="Entry"]; +// Entry -> Block0; +// Block0 [label="\ +// "]; +// Block0Exit [label="MainExit"]; +// Block0 -> Block0Exit; +// +// } diff --git a/test/libyul/yulControlFlowGraph/switch.yul b/test/libyul/yulControlFlowGraph/switch.yul new file mode 100644 index 000000000..2d6751d8a --- /dev/null +++ b/test/libyul/yulControlFlowGraph/switch.yul @@ -0,0 +1,68 @@ +{ + sstore(0, 0) + switch sload(0) + case 0 { + sstore(0x01, 0x0101) + } + case 1 { + sstore(0x02, 0x0101) + } + default { + sstore(0x03, 0x0101) + } + sstore(0x04, 0x0101) +} +// ---- +// digraph CFG { +// nodesep=0.7; +// node[shape=box]; +// +// Entry [label="Entry"]; +// Entry -> Block0; +// Block0 [label="\ +// sstore: [ 0x00 0x00 ] => [ ]\l\ +// sload: [ 0x00 ] => [ TMP[sload, 0] ]\l\ +// Assignment(GHOST[0]): [ TMP[sload, 0] ] => [ GHOST[0] ]\l\ +// eq: [ GHOST[0] 0x00 ] => [ TMP[eq, 0] ]\l\ +// "]; +// Block0 -> Block0Exit; +// Block0Exit [label="{ TMP[eq, 0]| { <0> Zero | <1> NonZero }}" shape=Mrecord]; +// Block0Exit:0 -> Block1; +// Block0Exit:1 -> Block2; +// +// Block1 [label="\ +// eq: [ GHOST[0] 0x01 ] => [ TMP[eq, 0] ]\l\ +// "]; +// Block1 -> Block1Exit; +// Block1Exit [label="{ TMP[eq, 0]| { <0> Zero | <1> NonZero }}" shape=Mrecord]; +// Block1Exit:0 -> Block3; +// Block1Exit:1 -> Block4; +// +// Block2 [label="\ +// sstore: [ 0x0101 0x01 ] => [ ]\l\ +// "]; +// Block2 -> Block2Exit [arrowhead=none]; +// Block2Exit [label="Jump" shape=oval]; +// Block2Exit -> Block5; +// +// Block3 [label="\ +// sstore: [ 0x0101 0x03 ] => [ ]\l\ +// "]; +// Block3 -> Block3Exit [arrowhead=none]; +// Block3Exit [label="Jump" shape=oval]; +// Block3Exit -> Block5; +// +// Block4 [label="\ +// sstore: [ 0x0101 0x02 ] => [ ]\l\ +// "]; +// Block4 -> Block4Exit [arrowhead=none]; +// Block4Exit [label="Jump" shape=oval]; +// Block4Exit -> Block5; +// +// Block5 [label="\ +// sstore: [ 0x0101 0x04 ] => [ ]\l\ +// "]; +// Block5Exit [label="MainExit"]; +// Block5 -> Block5Exit; +// +// } diff --git a/test/libyul/yulControlFlowGraph/variables.yul b/test/libyul/yulControlFlowGraph/variables.yul new file mode 100644 index 000000000..ecd274054 --- /dev/null +++ b/test/libyul/yulControlFlowGraph/variables.yul @@ -0,0 +1,31 @@ +{ + let x := calldataload(0) + let y := calldataload(2) + + x := calldataload(3) + y := calldataload(4) + + sstore(x,y) +} +// ---- +// digraph CFG { +// nodesep=0.7; +// node[shape=box]; +// +// Entry [label="Entry"]; +// Entry -> Block0; +// Block0 [label="\ +// calldataload: [ 0x00 ] => [ TMP[calldataload, 0] ]\l\ +// Assignment(x): [ TMP[calldataload, 0] ] => [ x ]\l\ +// calldataload: [ 0x02 ] => [ TMP[calldataload, 0] ]\l\ +// Assignment(y): [ TMP[calldataload, 0] ] => [ y ]\l\ +// calldataload: [ 0x03 ] => [ TMP[calldataload, 0] ]\l\ +// Assignment(x): [ TMP[calldataload, 0] ] => [ x ]\l\ +// calldataload: [ 0x04 ] => [ TMP[calldataload, 0] ]\l\ +// Assignment(y): [ TMP[calldataload, 0] ] => [ y ]\l\ +// sstore: [ y x ] => [ ]\l\ +// "]; +// Block0Exit [label="MainExit"]; +// Block0 -> Block0Exit; +// +// } diff --git a/test/libyul/yulControlFlowGraph/verbatim.yul b/test/libyul/yulControlFlowGraph/verbatim.yul new file mode 100644 index 000000000..05a4cc034 --- /dev/null +++ b/test/libyul/yulControlFlowGraph/verbatim.yul @@ -0,0 +1,26 @@ +{ + let a_1 := 42 + let a_2 := 23 + let a_3 := 1 + let b := verbatim_10i_1o("test", a_1, a_2, a_3, 2, 3, 4, 5, 6, 7, 8) + sstore(b,b) +} +// ---- +// digraph CFG { +// nodesep=0.7; +// node[shape=box]; +// +// Entry [label="Entry"]; +// Entry -> Block0; +// Block0 [label="\ +// Assignment(a_1): [ 0x2a ] => [ a_1 ]\l\ +// Assignment(a_2): [ 0x17 ] => [ a_2 ]\l\ +// Assignment(a_3): [ 0x01 ] => [ a_3 ]\l\ +// verbatim_10i_1o: [ 0x08 0x07 0x06 0x05 0x04 0x03 0x02 a_3 a_2 a_1 ] => [ TMP[verbatim_10i_1o, 0] ]\l\ +// Assignment(b): [ TMP[verbatim_10i_1o, 0] ] => [ b ]\l\ +// sstore: [ b b ] => [ ]\l\ +// "]; +// Block0Exit [label="MainExit"]; +// Block0 -> Block0Exit; +// +// } diff --git a/test/tools/CMakeLists.txt b/test/tools/CMakeLists.txt index 95eb3a636..1f62b57fc 100644 --- a/test/tools/CMakeLists.txt +++ b/test/tools/CMakeLists.txt @@ -32,6 +32,7 @@ add_executable(isoltest ../libsolidity/ASTJSONTest.cpp ../libsolidity/SMTCheckerTest.cpp ../libyul/Common.cpp + ../libyul/ControlFlowGraphTest.cpp ../libyul/EVMCodeTransformTest.cpp ../libyul/EwasmTranslationTest.cpp ../libyul/FunctionSideEffects.cpp @@ -41,4 +42,5 @@ add_executable(isoltest ../libyul/YulOptimizerTestCommon.cpp ../libyul/YulInterpreterTest.cpp ) -target_link_libraries(isoltest PRIVATE evmc libsolc solidity yulInterpreter evmasm Boost::boost Boost::program_options Boost::unit_test_framework) +target_compile_definitions(isoltest PRIVATE ISOLTEST) +target_link_libraries(isoltest PRIVATE evmc libsolc solidity yulInterpreter evmasm Boost::boost Boost::program_options Boost::unit_test_framework Threads::Threads)