diff --git a/libyul/backends/evm/SSAControlFlowGraph.h b/libyul/backends/evm/SSAControlFlowGraph.h index 8d802aa7873f..7918a152e670 100644 --- a/libyul/backends/evm/SSAControlFlowGraph.h +++ b/libyul/backends/evm/SSAControlFlowGraph.h @@ -116,7 +116,7 @@ class SSACFG std::list operations; std::variant exit = MainExit{}; template - void forEachExit(Callable&& _callable) + void forEachExit(Callable&& _callable) const { if (auto* jump = std::get_if(&exit)) _callable(jump->target); @@ -186,7 +186,7 @@ class SSACFG } ValueId newLiteral(u256 _value) { - auto [it, inserted] = m_literals.emplace(std::make_pair(_value, SSACFG::ValueId{m_valueInfos.size()})); + auto [it, inserted] = m_literals.emplace(_value, SSACFG::ValueId{m_valueInfos.size()}); if (inserted) m_valueInfos.emplace_back(LiteralValue{_value}); else diff --git a/libyul/backends/evm/SSAControlFlowGraphBuilder.cpp b/libyul/backends/evm/SSAControlFlowGraphBuilder.cpp index fefe44df115b..1a5b0afca718 100644 --- a/libyul/backends/evm/SSAControlFlowGraphBuilder.cpp +++ b/libyul/backends/evm/SSAControlFlowGraphBuilder.cpp @@ -178,14 +178,6 @@ void cleanUnreachable(SSACFG& _cfg) { auto& block = _cfg.block(blockId); - for (auto phi: block.phis) - { - auto& phiValue = std::get(_cfg.valueInfo(phi)); - yulAssert(block.entries.size() == phiValue.arguments.size()); - for (auto&& [entry, arg]: ranges::zip_view(block.entries, phiValue.arguments)) - yulAssert((reachabilityCheck.visited.count(entry) == 0) == isUnreachableValue(arg)); - } - std::set maybeTrivialPhi; for (auto it = block.entries.begin(); it != block.entries.end();) if (reachabilityCheck.visited.count(*it)) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 0d19d77960d2..6f7b237ae222 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -151,6 +151,8 @@ set(libyul_sources libyul/ObjectCompilerTest.h libyul/ObjectParser.cpp libyul/Parser.cpp + libyul/SSAControlFlowGraphTest.cpp + libyul/SSAControlFlowGraphTest.h libyul/StackLayoutGeneratorTest.cpp libyul/StackLayoutGeneratorTest.h libyul/StackShufflingTest.cpp diff --git a/test/InteractiveTests.h b/test/InteractiveTests.h index 42e20d7e3919..cc60e45285e9 100644 --- a/test/InteractiveTests.h +++ b/test/InteractiveTests.h @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -66,6 +67,7 @@ Testsuite const g_interactiveTestsuites[] = { {"Yul Interpreter", "libyul", "yulInterpreterTests", false, false, &yul::test::YulInterpreterTest::create}, {"Yul Object Compiler", "libyul", "objectCompiler", false, false, &yul::test::ObjectCompilerTest::create}, {"Yul Control Flow Graph", "libyul", "yulControlFlowGraph", false, false, &yul::test::ControlFlowGraphTest::create}, + {"Yul SSA Control Flow Graph", "libyul", "yulSSAControlFlowGraph", false, false, &yul::test::SSAControlFlowGraphTest::create}, {"Yul Stack Layout", "libyul", "yulStackLayout", false, false, &yul::test::StackLayoutGeneratorTest::create}, {"Yul Stack Shuffling", "libyul", "yulStackShuffling", false, false, &yul::test::StackShufflingTest::create}, {"Control Flow Side Effects", "libyul", "controlFlowSideEffects", false, false, &yul::test::ControlFlowSideEffectsTest::create}, diff --git a/test/libyul/ControlFlowGraphTest.cpp b/test/libyul/ControlFlowGraphTest.cpp index 5c9bba8d41e5..0060b850efbb 100644 --- a/test/libyul/ControlFlowGraphTest.cpp +++ b/test/libyul/ControlFlowGraphTest.cpp @@ -130,7 +130,6 @@ class ControlFlowGraphPrinter }, [&](CFG::BuiltinCall const& _call) { m_stream << _call.functionCall.get().functionName.name.str() << ": "; - }, [&](CFG::Assignment const& _assignment) { m_stream << "Assignment("; diff --git a/test/libyul/SSAControlFlowGraphTest.cpp b/test/libyul/SSAControlFlowGraphTest.cpp new file mode 100644 index 000000000000..748b32baf933 --- /dev/null +++ b/test/libyul/SSAControlFlowGraphTest.cpp @@ -0,0 +1,319 @@ +/* + 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 + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wtautological-compare" +#include +#pragma GCC diagnostic pop + +#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; + +std::unique_ptr SSAControlFlowGraphTest::create(TestCase::Config const& _config) { + return std::make_unique(_config.filename); +} + +SSAControlFlowGraphTest::SSAControlFlowGraphTest(std::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(); +} + +class SSACFGPrinter +{ +public: + SSACFGPrinter(SSACFG const& _ssacfg, SSACFG::BlockId _blockId): m_ssacfg(_ssacfg) + { + printBlock(_blockId); + } + SSACFGPrinter(SSACFG const& _ssacfg, Scope::Function const& _function): m_ssacfg(_ssacfg) + { + printFunction(_function); + } + friend std::ostream& operator<<(std::ostream& stream, SSACFGPrinter const& printer) { + stream << printer.m_result.str(); + return stream; + } +private: + std::string varToString(SSACFG::ValueId _var) { + if (_var.value == std::numeric_limits::max()) + return "INVALID"; + auto const& info = m_ssacfg.valueInfo(_var); + return std::visit( + util::GenericVisitor{ + [&](SSACFG::UnreachableValue const&) -> std::string { + return "[unreachable]"; + }, + [&](SSACFG::PhiValue const&) -> std::string { + return fmt::format("v{}", _var.value); + }, + [&](SSACFG::VariableValue const&) -> std::string { + return fmt::format("v{}", _var.value); + }, + [&](SSACFG::LiteralValue const& _literal) -> std::string { + return util::formatNumberReadable(_literal.value); + } + }, + info + ); + } + + std::string formatPhi(SSACFG::PhiValue const& _phiValue) + { + auto const transform = [this](SSACFG::ValueId const& valueId) { return varToString(valueId); }; + std::vector formattedArgs; + formattedArgs.reserve(_phiValue.arguments.size()); + for(auto const& [arg, entry] : ranges::zip_view(_phiValue.arguments | ranges::views::transform(transform), m_ssacfg.block(_phiValue.block).entries)) + { + formattedArgs.push_back(fmt::format("Block {} => {}", entry.value, arg)); + } + return fmt::format("φ(\\l\\\n\t{}\\l\\\n)", fmt::join(formattedArgs, ",\\l\\\n\t")); + } + + void writeBlock(SSACFG::BlockId const& _id, SSACFG::BasicBlock const& _block) + { + auto const transform = [this](SSACFG::ValueId const& valueId) { return varToString(valueId); }; + bool entryBlock = _id.value == 0; + if (entryBlock) + { + m_result << "Entry [label=\"Entry\"];\n"; + m_result << fmt::format("Entry -> Block{};\n", _id.value); + } + { + m_result << fmt::format("Block{0} [label=\"\\\nBlock {0}\\n", _id.value); + for (auto const& phi : _block.phis) + { + auto const* phiValue = std::get_if(&m_ssacfg.valueInfo(phi)); + solAssert(phiValue); + m_result << fmt::format("v{} := {}\\l\\\n", phi.value, formatPhi(*phiValue)); + } + for (auto const& operation : _block.operations) + { + std::string const label = std::visit(util::GenericVisitor{ + [&](SSACFG::Call const& _call) { + return _call.function.get().name.str(); + }, + [&](SSACFG::BuiltinCall const& _call) { + return _call.builtin.get().name.str(); + }, + }, operation.kind); + if (operation.outputs.empty()) + m_result << fmt::format( + "{}({})\\l\\\n", + label, + fmt::join(operation.inputs | ranges::views::transform(transform), ", ") + ); + else + m_result << fmt::format( + "{} := {}({})\\l\\\n", + fmt::join(operation.outputs | ranges::views::transform(transform), ", "), + label, + fmt::join(operation.inputs | ranges::views::transform(transform), ", ") + ); + } + m_result << "\"];\n"; + std::visit(util::GenericVisitor{ + [&](SSACFG::BasicBlock::MainExit const&) + { + m_result << fmt::format("Block{}Exit [label=\"MainExit\"];\n", _id.value); + m_result << fmt::format("Block{0} -> Block{0}Exit;\n", _id.value); + }, + [&](SSACFG::BasicBlock::Jump const& _jump) + { + m_result << fmt::format("Block{0} -> Block{0}Exit [arrowhead=none];\n", _id.value); + m_result << fmt::format("Block{}Exit [label=\"Jump\" shape=oval];\n", _id.value); + m_result << fmt::format("Block{}Exit -> Block{};\n", _id.value, _jump.target.value); + }, + [&](SSACFG::BasicBlock::ConditionalJump const& _conditionalJump) + { + m_result << "Block" << _id.value << " -> Block" << _id.value << "Exit;\n"; + m_result << "Block" << _id.value << "Exit [label=\"{ If "; + m_result << varToString(_conditionalJump.condition); + m_result << "| { <0> Zero | <1> NonZero }}\" shape=Mrecord];\n"; + m_result << "Block" << _id.value; + m_result << "Exit:0 -> Block" << _conditionalJump.zero.value << ";\n"; + m_result << "Block" << _id.value; + m_result << "Exit:1 -> Block" << _conditionalJump.nonZero.value << ";\n"; + }, + [&](SSACFG::BasicBlock::JumpTable const& jt) + { + m_result << "Block" << _id.value << " -> Block" << _id.value << "Exit;\n"; + std::string options; + for(const auto& jumpCase : jt.cases) + { + if (!options.empty()) + options += " | "; + options += fmt::format("<{0}> {0}", formatNumber(jumpCase.first)); + } + if (!options.empty()) + options += " | "; + options += " default"; + m_result << fmt::format("Block{}Exit [label=\"{{ JT | {{ {} }} }}\" shape=Mrecord];\n", _id.value, options); + for(const auto& jumpCase : jt.cases) + { + m_result << fmt::format("Block{}Exit:{} -> Block{};\n", _id.value, formatNumber(jumpCase.first), jumpCase.second.value); + } + m_result << fmt::format("Block{}Exit:default -> Block{};\n", _id.value, jt.defaultCase.value); + }, + [&](SSACFG::BasicBlock::FunctionReturn const& fr) + { + m_result << "Block" << _id.value << "Exit [label=\"FunctionReturn[" + << fmt::format("{}", fmt::join(fr.returnValues | ranges::views::transform(transform), ", ")) + << "]\"];\n"; + m_result << "Block" << _id.value << " -> Block" << _id.value << "Exit;\n"; + }, + [&](SSACFG::BasicBlock::Terminated const&) + { + m_result << "Block" << _id.value << "Exit [label=\"Terminated\"];\n"; + m_result << "Block" << _id.value << " -> Block" << _id.value << "Exit;\n"; + } + }, _block.exit); + } + + } + + void printBlock(SSACFG::BlockId const& _rootId) + { + std::set explored{}; + explored.insert(_rootId); + + std::deque toVisit{}; + toVisit.emplace_back(_rootId); + + while(!toVisit.empty()) + { + auto const id = toVisit.front(); + toVisit.pop_front(); + auto const& block = m_ssacfg.block(id); + writeBlock(id, block); + block.forEachExit( + [&](SSACFG::BlockId const& _exitBlock) + { + if (explored.count(_exitBlock) == 0) + { + explored.insert(_exitBlock); + toVisit.emplace_back(_exitBlock); + } + } + ); + } + } + + void printFunction(Scope::Function const& _fun) + { + static auto constexpr returnsTransform = [](auto const& functionReturnValue) { return functionReturnValue.get().name.str(); }; + static auto constexpr argsTransform = [](auto const& arg) { return fmt::format("v{}", std::get<1>(arg).value); }; + auto const& info = m_ssacfg.functionInfos.at(&_fun); + m_result << "FunctionEntry_" << _fun.name.str() << "_" << info.entry.value << " [label=\""; + if (!info.returns.empty()) + m_result << fmt::format("function {0}:\n {1} := {0}({2})", _fun.name.str(), fmt::join(info.returns | ranges::views::transform(returnsTransform), ", "), fmt::join(info.arguments | ranges::views::transform(argsTransform), ", ")); + else + m_result << fmt::format("function {0}:\n {0}({1})", _fun.name.str(), fmt::join(info.arguments | ranges::views::transform(argsTransform), ", ")); + m_result << "\"];\n"; + m_result << "FunctionEntry_" << _fun.name.str() << "_" << info.entry.value << " -> Block" << info.entry.value << ";\n"; + printBlock(info.entry); + } + + SSACFG const& m_ssacfg; + std::stringstream m_result{}; +}; + +TestCase::TestResult SSAControlFlowGraphTest::run(std::ostream& _stream, std::string const& _linePrefix, bool const _formatted) +{ + ErrorList errors; + auto [object, analysisInfo] = parse(m_source, *m_dialect, errors); + if (!object || !analysisInfo || Error::containsErrors(errors)) + { + AnsiColorized(_stream, _formatted, {formatting::BOLD, formatting::RED}) << _linePrefix << "Error parsing source." << std::endl; + return TestResult::FatalError; + } + + std::ostringstream output; + + auto info = AsmAnalyzer::analyzeStrictAssertCorrect(*m_dialect, *object); + auto ssaCfg = SSAControlFlowGraphBuilder::build( + info, + *m_dialect, + *object->code + ); + + output << "digraph SSACFG {\nnodesep=0.7;\nnode[shape=box];\n\n"; + output << SSACFGPrinter(*ssaCfg, SSACFG::BlockId{0}); + for (auto function: ssaCfg->functions) + output << SSACFGPrinter(*ssaCfg, function.get()); + 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/SSAControlFlowGraphTest.h b/test/libyul/SSAControlFlowGraphTest.h new file mode 100644 index 000000000000..f976be03c339 --- /dev/null +++ b/test/libyul/SSAControlFlowGraphTest.h @@ -0,0 +1,42 @@ +/* +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 + +#include + +namespace solidity::yul +{ +struct Dialect; + +namespace test +{ + +class SSAControlFlowGraphTest: public solidity::frontend::test::TestCase +{ +public: + static std::unique_ptr create(Config const& _config); + explicit SSAControlFlowGraphTest(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/yulSSAControlFlowGraph/complex.yul b/test/libyul/yulSSAControlFlowGraph/complex.yul new file mode 100644 index 000000000000..81a4d01e534b --- /dev/null +++ b/test/libyul/yulSSAControlFlowGraph/complex.yul @@ -0,0 +1,178 @@ +{ + 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 SSACFG { +// nodesep=0.7; +// node[shape=box]; +// +// Entry [label="Entry"]; +// Entry -> Block0; +// Block0 [label="\ +// Block 0\nv78 := f(2, 1)\l\ +// pop(v78)\l\ +// "]; +// Block0Exit [label="MainExit"]; +// Block0 -> Block0Exit; +// FunctionEntry_f_1 [label="function f: +// c := f(v0, v1)"]; +// FunctionEntry_f_1 -> Block1; +// Block1 [label="\ +// Block 1\n"]; +// Block1 -> Block1Exit [arrowhead=none]; +// Block1Exit [label="Jump" shape=oval]; +// Block1Exit -> Block2; +// Block2 [label="\ +// Block 2\nv4 := φ(\l\ +// Block 1 => 42,\l\ +// Block 22 => v43\l\ +// )\l\ +// v5 := lt(v0, v4)\l\ +// "]; +// Block2 -> Block2Exit; +// Block2Exit [label="{ If v5| { <0> Zero | <1> NonZero }}" shape=Mrecord]; +// Block2Exit:0 -> Block5; +// Block2Exit:1 -> Block3; +// Block3 [label="\ +// Block 3\nv6 := mload(v4)\l\ +// v7 := eq(0, v6)\l\ +// "]; +// Block3 -> Block3Exit; +// Block3Exit [label="{ If v7| { <0> Zero | <1> NonZero }}" shape=Mrecord]; +// Block3Exit:0 -> Block8; +// Block3Exit:1 -> Block7; +// Block5 [label="\ +// Block 5\nsstore(3084, 12)\l\ +// "]; +// Block5Exit [label="FunctionReturn[v17]"]; +// Block5 -> Block5Exit; +// Block7 [label="\ +// Block 7\nsstore(514, 2)\l\ +// "]; +// Block7 -> Block7Exit [arrowhead=none]; +// Block7Exit [label="Jump" shape=oval]; +// Block7Exit -> Block5; +// Block8 [label="\ +// Block 8\nv13 := eq(1, v6)\l\ +// "]; +// Block8 -> Block8Exit; +// Block8Exit [label="{ If v13| { <0> Zero | <1> NonZero }}" shape=Mrecord]; +// Block8Exit:0 -> Block11; +// Block8Exit:1 -> Block10; +// Block10 [label="\ +// Block 10\nsstore(1028, 4)\l\ +// "]; +// Block10Exit [label="FunctionReturn[v17]"]; +// Block10 -> Block10Exit; +// Block11 [label="\ +// Block 11\nv20 := eq(2, v6)\l\ +// "]; +// Block11 -> Block11Exit; +// Block11Exit [label="{ If v20| { <0> Zero | <1> NonZero }}" shape=Mrecord]; +// Block11Exit:0 -> Block14; +// Block11Exit:1 -> Block13; +// Block13 [label="\ +// Block 13\nsstore(1542, 6)\l\ +// revert(0, 0)\l\ +// "]; +// Block13Exit [label="Terminated"]; +// Block13 -> Block13Exit; +// Block14 [label="\ +// Block 14\nv25 := eq(3, v6)\l\ +// "]; +// Block14 -> Block14Exit; +// Block14Exit [label="{ If v25| { <0> Zero | <1> NonZero }}" shape=Mrecord]; +// Block14Exit:0 -> Block17; +// Block14Exit:1 -> Block16; +// Block16 [label="\ +// Block 16\nsstore(2056, 8)\l\ +// "]; +// Block16 -> Block16Exit [arrowhead=none]; +// Block16Exit [label="Jump" shape=oval]; +// Block16Exit -> Block6; +// Block17 [label="\ +// Block 17\nv29 := mload(v1)\l\ +// "]; +// Block17 -> Block17Exit; +// Block17Exit [label="{ If v29| { <0> Zero | <1> NonZero }}" shape=Mrecord]; +// Block17Exit:0 -> Block19; +// Block17Exit:1 -> Block18; +// Block6 [label="\ +// Block 6\nsstore(2827, 11)\l\ +// "]; +// Block6 -> Block6Exit [arrowhead=none]; +// Block6Exit [label="Jump" shape=oval]; +// Block6Exit -> Block4; +// Block18 [label="\ +// Block 18\nreturn(0, 0)\l\ +// "]; +// Block18Exit [label="Terminated"]; +// Block18 -> Block18Exit; +// Block19 [label="\ +// Block 19\nsstore(2570, 10)\l\ +// "]; +// Block19 -> Block19Exit [arrowhead=none]; +// Block19Exit [label="Jump" shape=oval]; +// Block19Exit -> Block6; +// Block4 [label="\ +// Block 4\nv43 := add(1, v4)\l\ +// v44 := calldataload(v43)\l\ +// "]; +// Block4 -> Block4Exit; +// Block4Exit [label="{ If v44| { <0> Zero | <1> NonZero }}" shape=Mrecord]; +// Block4Exit:0 -> Block22; +// Block4Exit:1 -> Block21; +// Block21 [label="\ +// Block 21\nsstore(v43, 0)\l\ +// "]; +// Block21Exit [label="FunctionReturn[v45]"]; +// Block21 -> Block21Exit; +// Block22 [label="\ +// Block 22\nsstore(65535, 255)\l\ +// "]; +// Block22 -> Block22Exit [arrowhead=none]; +// Block22Exit [label="Jump" shape=oval]; +// Block22Exit -> Block2; +// } diff --git a/test/libyul/yulSSAControlFlowGraph/complex2.yul b/test/libyul/yulSSAControlFlowGraph/complex2.yul new file mode 100644 index 000000000000..0dc4dfda6e95 --- /dev/null +++ b/test/libyul/yulSSAControlFlowGraph/complex2.yul @@ -0,0 +1,194 @@ +{ + 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) + c:=27 + } + sstore(0x1,0x1) + pop(f(1,2)) + let z:= add(5,sload(0)) + let w := f(z,sload(4)) + sstore(z,w) + let x := f(w,sload(5)) + sstore(0x1,x) +} +// ---- +// digraph SSACFG { +// nodesep=0.7; +// node[shape=box]; +// +// Entry [label="Entry"]; +// Entry -> Block0; +// Block0 [label="\ +// Block 0\nsstore(1, 1)\l\ +// v78 := f(2, 1)\l\ +// pop(v78)\l\ +// v79 := sload(0)\l\ +// v80 := add(v79, 5)\l\ +// v81 := sload(4)\l\ +// v82 := f(v81, v80)\l\ +// sstore(v82, v80)\l\ +// v83 := sload(5)\l\ +// v84 := f(v83, v82)\l\ +// sstore(v84, 1)\l\ +// "]; +// Block0Exit [label="MainExit"]; +// Block0 -> Block0Exit; +// FunctionEntry_f_1 [label="function f: +// c := f(v0, v1)"]; +// FunctionEntry_f_1 -> Block1; +// Block1 [label="\ +// Block 1\n"]; +// Block1 -> Block1Exit [arrowhead=none]; +// Block1Exit [label="Jump" shape=oval]; +// Block1Exit -> Block2; +// Block2 [label="\ +// Block 2\nv4 := φ(\l\ +// Block 1 => 42,\l\ +// Block 22 => v43\l\ +// )\l\ +// v5 := lt(v0, v4)\l\ +// "]; +// Block2 -> Block2Exit; +// Block2Exit [label="{ If v5| { <0> Zero | <1> NonZero }}" shape=Mrecord]; +// Block2Exit:0 -> Block5; +// Block2Exit:1 -> Block3; +// Block3 [label="\ +// Block 3\nv6 := mload(v4)\l\ +// v7 := eq(0, v6)\l\ +// "]; +// Block3 -> Block3Exit; +// Block3Exit [label="{ If v7| { <0> Zero | <1> NonZero }}" shape=Mrecord]; +// Block3Exit:0 -> Block8; +// Block3Exit:1 -> Block7; +// Block5 [label="\ +// Block 5\nsstore(3084, 12)\l\ +// "]; +// Block5Exit [label="FunctionReturn[27]"]; +// Block5 -> Block5Exit; +// Block7 [label="\ +// Block 7\nsstore(514, 2)\l\ +// "]; +// Block7 -> Block7Exit [arrowhead=none]; +// Block7Exit [label="Jump" shape=oval]; +// Block7Exit -> Block5; +// Block8 [label="\ +// Block 8\nv13 := eq(1, v6)\l\ +// "]; +// Block8 -> Block8Exit; +// Block8Exit [label="{ If v13| { <0> Zero | <1> NonZero }}" shape=Mrecord]; +// Block8Exit:0 -> Block11; +// Block8Exit:1 -> Block10; +// Block10 [label="\ +// Block 10\nsstore(1028, 4)\l\ +// "]; +// Block10Exit [label="FunctionReturn[v17]"]; +// Block10 -> Block10Exit; +// Block11 [label="\ +// Block 11\nv20 := eq(2, v6)\l\ +// "]; +// Block11 -> Block11Exit; +// Block11Exit [label="{ If v20| { <0> Zero | <1> NonZero }}" shape=Mrecord]; +// Block11Exit:0 -> Block14; +// Block11Exit:1 -> Block13; +// Block13 [label="\ +// Block 13\nsstore(1542, 6)\l\ +// revert(0, 0)\l\ +// "]; +// Block13Exit [label="Terminated"]; +// Block13 -> Block13Exit; +// Block14 [label="\ +// Block 14\nv25 := eq(3, v6)\l\ +// "]; +// Block14 -> Block14Exit; +// Block14Exit [label="{ If v25| { <0> Zero | <1> NonZero }}" shape=Mrecord]; +// Block14Exit:0 -> Block17; +// Block14Exit:1 -> Block16; +// Block16 [label="\ +// Block 16\nsstore(2056, 8)\l\ +// "]; +// Block16 -> Block16Exit [arrowhead=none]; +// Block16Exit [label="Jump" shape=oval]; +// Block16Exit -> Block6; +// Block17 [label="\ +// Block 17\nv29 := mload(v1)\l\ +// "]; +// Block17 -> Block17Exit; +// Block17Exit [label="{ If v29| { <0> Zero | <1> NonZero }}" shape=Mrecord]; +// Block17Exit:0 -> Block19; +// Block17Exit:1 -> Block18; +// Block6 [label="\ +// Block 6\nsstore(2827, 11)\l\ +// "]; +// Block6 -> Block6Exit [arrowhead=none]; +// Block6Exit [label="Jump" shape=oval]; +// Block6Exit -> Block4; +// Block18 [label="\ +// Block 18\nreturn(0, 0)\l\ +// "]; +// Block18Exit [label="Terminated"]; +// Block18 -> Block18Exit; +// Block19 [label="\ +// Block 19\nsstore(2570, 10)\l\ +// "]; +// Block19 -> Block19Exit [arrowhead=none]; +// Block19Exit [label="Jump" shape=oval]; +// Block19Exit -> Block6; +// Block4 [label="\ +// Block 4\nv43 := add(1, v4)\l\ +// v44 := calldataload(v43)\l\ +// "]; +// Block4 -> Block4Exit; +// Block4Exit [label="{ If v44| { <0> Zero | <1> NonZero }}" shape=Mrecord]; +// Block4Exit:0 -> Block22; +// Block4Exit:1 -> Block21; +// Block21 [label="\ +// Block 21\nsstore(v43, 0)\l\ +// "]; +// Block21Exit [label="FunctionReturn[v45]"]; +// Block21 -> Block21Exit; +// Block22 [label="\ +// Block 22\nsstore(65535, 255)\l\ +// "]; +// Block22 -> Block22Exit [arrowhead=none]; +// Block22Exit [label="Jump" shape=oval]; +// Block22Exit -> Block2; +// } diff --git a/test/libyul/yulSSAControlFlowGraph/function.yul b/test/libyul/yulSSAControlFlowGraph/function.yul new file mode 100644 index 000000000000..dea6a7f4c504 --- /dev/null +++ b/test/libyul/yulSSAControlFlowGraph/function.yul @@ -0,0 +1,67 @@ +{ + 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 SSACFG { +// nodesep=0.7; +// node[shape=box]; +// +// Entry [label="Entry"]; +// Entry -> Block0; +// Block0 [label="\ +// Block 0\nv11, v12 := i()\l\ +// h(v11)\l\ +// "]; +// Block0Exit [label="Terminated"]; +// Block0 -> Block0Exit; +// FunctionEntry_f_1 [label="function f: +// r := f(v0, v1)"]; +// FunctionEntry_f_1 -> Block1; +// Block1 [label="\ +// Block 1\nv3 := add(v1, v0)\l\ +// v4 := sub(v0, v3)\l\ +// "]; +// Block1Exit [label="FunctionReturn[v4]"]; +// Block1 -> Block1Exit; +// FunctionEntry_g_2 [label="function g: +// g()"]; +// FunctionEntry_g_2 -> Block2; +// Block2 [label="\ +// Block 2\nsstore(257, 1)\l\ +// "]; +// Block2Exit [label="FunctionReturn[]"]; +// Block2 -> Block2Exit; +// FunctionEntry_h_3 [label="function h: +// h(v2)"]; +// FunctionEntry_h_3 -> Block3; +// Block3 [label="\ +// Block 3\nv8 := f(0, v2)\l\ +// h(v8)\l\ +// "]; +// Block3Exit [label="Terminated"]; +// Block3 -> Block3Exit; +// FunctionEntry_i_4 [label="function i: +// v, w := i()"]; +// FunctionEntry_i_4 -> Block4; +// Block4 [label="\ +// Block 4\n"]; +// Block4Exit [label="FunctionReturn[514, 771]"]; +// Block4 -> Block4Exit; +// } diff --git a/test/libyul/yulSSAControlFlowGraph/if.yul b/test/libyul/yulSSAControlFlowGraph/if.yul new file mode 100644 index 000000000000..97c5e3bdd59d --- /dev/null +++ b/test/libyul/yulSSAControlFlowGraph/if.yul @@ -0,0 +1,39 @@ +{ + let x := calldataload(3) + if 0 { + x := calldataload(77) + } + let y := calldataload(x) + sstore(y, 0) +} +// ---- +// digraph SSACFG { +// nodesep=0.7; +// node[shape=box]; +// +// Entry [label="Entry"]; +// Entry -> Block0; +// Block0 [label="\ +// Block 0\nv1 := calldataload(3)\l\ +// "]; +// Block0 -> Block0Exit; +// Block0Exit [label="{ If 0| { <0> Zero | <1> NonZero }}" shape=Mrecord]; +// Block0Exit:0 -> Block2; +// Block0Exit:1 -> Block1; +// Block1 [label="\ +// Block 1\nv4 := calldataload(77)\l\ +// "]; +// Block1 -> Block1Exit [arrowhead=none]; +// Block1Exit [label="Jump" shape=oval]; +// Block1Exit -> Block2; +// Block2 [label="\ +// Block 2\nv5 := φ(\l\ +// Block 0 => v1,\l\ +// Block 1 => v4\l\ +// )\l\ +// v6 := calldataload(v5)\l\ +// sstore(0, v6)\l\ +// "]; +// Block2Exit [label="MainExit"]; +// Block2 -> Block2Exit; +// } diff --git a/test/libyul/yulSSAControlFlowGraph/switch.yul b/test/libyul/yulSSAControlFlowGraph/switch.yul new file mode 100644 index 000000000000..b69cd7305983 --- /dev/null +++ b/test/libyul/yulSSAControlFlowGraph/switch.yul @@ -0,0 +1,67 @@ +{ + let x := calldataload(3) + + switch sload(0) + case 0 { + x := calldataload(77) + } + case 1 { + x := calldataload(88) + } + default { + x := calldataload(99) + } + sstore(x, 0) +} +// ---- +// digraph SSACFG { +// nodesep=0.7; +// node[shape=box]; +// +// Entry [label="Entry"]; +// Entry -> Block0; +// Block0 [label="\ +// Block 0\nv1 := calldataload(3)\l\ +// v3 := sload(0)\l\ +// v4 := eq(0, v3)\l\ +// "]; +// Block0 -> Block0Exit; +// Block0Exit [label="{ If v4| { <0> Zero | <1> NonZero }}" shape=Mrecord]; +// Block0Exit:0 -> Block3; +// Block0Exit:1 -> Block2; +// Block2 [label="\ +// Block 2\nv6 := calldataload(77)\l\ +// "]; +// Block2 -> Block2Exit [arrowhead=none]; +// Block2Exit [label="Jump" shape=oval]; +// Block2Exit -> Block1; +// Block3 [label="\ +// Block 3\nv7 := eq(1, v3)\l\ +// "]; +// Block3 -> Block3Exit; +// Block3Exit [label="{ If v7| { <0> Zero | <1> NonZero }}" shape=Mrecord]; +// Block3Exit:0 -> Block5; +// Block3Exit:1 -> Block4; +// Block1 [label="\ +// Block 1\nv13 := φ(\l\ +// Block 2 => v6,\l\ +// Block 4 => v10,\l\ +// Block 5 => v12\l\ +// )\l\ +// sstore(0, v13)\l\ +// "]; +// Block1Exit [label="MainExit"]; +// Block1 -> Block1Exit; +// Block4 [label="\ +// Block 4\nv10 := calldataload(88)\l\ +// "]; +// Block4 -> Block4Exit [arrowhead=none]; +// Block4Exit [label="Jump" shape=oval]; +// Block4Exit -> Block1; +// Block5 [label="\ +// Block 5\nv12 := calldataload(99)\l\ +// "]; +// Block5 -> Block5Exit [arrowhead=none]; +// Block5Exit [label="Jump" shape=oval]; +// Block5Exit -> Block1; +// } diff --git a/test/tools/CMakeLists.txt b/test/tools/CMakeLists.txt index 532a08d9fc55..9bad89776678 100644 --- a/test/tools/CMakeLists.txt +++ b/test/tools/CMakeLists.txt @@ -38,6 +38,7 @@ add_executable(isoltest ../libsolidity/SMTCheckerTest.cpp ../libyul/Common.cpp ../libyul/ControlFlowGraphTest.cpp + ../libyul/SSAControlFlowGraphTest.cpp ../libyul/ControlFlowSideEffectsTest.cpp ../libyul/EVMCodeTransformTest.cpp ../libyul/FunctionSideEffects.cpp