Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions libsolidity/interface/CompilerStack.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -924,6 +924,16 @@ Json const& CompilerStack::yulIRAst(std::string const& _contractName) const
return contract(_contractName).yulIRAst;
}

Json const& CompilerStack::yulCFGJson(std::string const& _contractName) const
{
if (m_stackState != CompilationSuccessful)
solThrow(CompilerError, "Compilation was not successful.");

solUnimplementedAssert(!isExperimentalSolidity());

return contract(_contractName).yulCFGJson;
}

std::string const& CompilerStack::yulIROptimized(std::string const& _contractName) const
{
solAssert(m_stackState == CompilationSuccessful, "Compilation was not successful.");
Expand Down Expand Up @@ -1506,6 +1516,7 @@ void CompilerStack::generateIR(ContractDefinition const& _contract, bool _unopti
);

compiledContract.yulIRAst = stack.astJson();
compiledContract.yulCFGJson = stack.cfgJson();
if (!_unoptimizedOnly)
{
stack.optimize();
Expand Down
3 changes: 3 additions & 0 deletions libsolidity/interface/CompilerStack.h
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,8 @@ class CompilerStack: public langutil::CharStreamProvider, public evmasm::Abstrac
/// @returns the optimized IR representation of a contract AST in JSON format.
Json const& yulIROptimizedAst(std::string const& _contractName) const;

Json const& yulCFGJson(std::string const& _contractName) const;

/// @returns the assembled object for a contract.
virtual evmasm::LinkerObject const& object(std::string const& _contractName) const override;

Expand Down Expand Up @@ -411,6 +413,7 @@ class CompilerStack: public langutil::CharStreamProvider, public evmasm::Abstrac
std::string yulIROptimized; ///< Reparsed and possibly optimized Yul IR code.
Json yulIRAst; ///< JSON AST of Yul IR code.
Json yulIROptimizedAst; ///< JSON AST of optimized Yul IR code.
Json yulCFGJson; ///< JSON CFG of Yul IR code.
util::LazyInit<std::string const> metadata; ///< The metadata json that will be hashed into the chain.
util::LazyInit<Json const> abi;
util::LazyInit<Json const> storageLayout;
Expand Down
13 changes: 10 additions & 3 deletions libsolidity/interface/StandardCompiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ bool hashMatchesContent(std::string const& _hash, std::string const& _content)

bool isArtifactRequested(Json const& _outputSelection, std::string const& _artifact, bool _wildcardMatchesExperimental)
{
static std::set<std::string> experimental{"ir", "irAst", "irOptimized", "irOptimizedAst"};
static std::set<std::string> experimental{"ir", "irAst", "irOptimized", "irOptimizedAst", "yulCFGJson"};
for (auto const& selectedArtifactJson: _outputSelection)
{
std::string const& selectedArtifact = selectedArtifactJson.get<std::string>();
Expand All @@ -191,6 +191,9 @@ bool isArtifactRequested(Json const& _outputSelection, std::string const& _artif
return true;
else if (selectedArtifact == "*")
{
// TODO: yulCFGJson is only experimental now, so it should not be matched by "*".
if (_artifact == "yulCFGJson")
return false;
// "ir", "irOptimized" can only be matched by "*" if activated.
if (experimental.count(_artifact) == 0 || _wildcardMatchesExperimental)
return true;
Expand Down Expand Up @@ -264,7 +267,7 @@ bool isBinaryRequested(Json const& _outputSelection)
// This does not include "evm.methodIdentifiers" on purpose!
static std::vector<std::string> const outputsThatRequireBinaries = std::vector<std::string>{
"*",
"ir", "irAst", "irOptimized", "irOptimizedAst",
"ir", "irAst", "irOptimized", "irOptimizedAst", "yulCFGJson",
"evm.gasEstimates", "evm.legacyAssembly", "evm.assembly"
} + evmObjectComponents("bytecode") + evmObjectComponents("deployedBytecode");

Expand Down Expand Up @@ -307,7 +310,7 @@ CompilerStack::IROutputSelection irOutputSelection(Json const& _outputSelection)
for (auto const& requests: fileRequests)
for (auto const& request: requests)
{
if (request == "irOptimized" || request == "irOptimizedAst")
if (request == "irOptimized" || request == "irOptimizedAst" || request == "yulCFGJson")
return CompilerStack::IROutputSelection::UnoptimizedAndOptimized;

if (request == "ir" || request == "irAst")
Expand Down Expand Up @@ -1485,6 +1488,8 @@ Json StandardCompiler::compileSolidity(StandardCompiler::InputsAndSettings _inpu
contractData["irOptimized"] = compilerStack.yulIROptimized(contractName);
if (compilationSuccess && isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "irOptimizedAst", wildcardMatchesExperimental))
contractData["irOptimizedAst"] = compilerStack.yulIROptimizedAst(contractName);
if (compilationSuccess && isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "yulCFGJson", wildcardMatchesExperimental))
contractData["yulCFGJson"] = compilerStack.yulCFGJson(contractName);

// EVM
Json evmData;
Expand Down Expand Up @@ -1698,6 +1703,8 @@ Json StandardCompiler::compileYul(InputsAndSettings _inputsAndSettings)
output["contracts"][sourceName][contractName]["irOptimized"] = stack.print();
if (isArtifactRequested(_inputsAndSettings.outputSelection, sourceName, contractName, "evm.assembly", wildcardMatchesExperimental))
output["contracts"][sourceName][contractName]["evm"]["assembly"] = object.assembly->assemblyString(stack.debugInfoSelection());
if (isArtifactRequested(_inputsAndSettings.outputSelection, sourceName, contractName, "yulCFGJson", wildcardMatchesExperimental))
output["contracts"][sourceName][contractName]["yulCFGJson"] = stack.cfgJson();

return output;
}
Expand Down
2 changes: 2 additions & 0 deletions libyul/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ add_library(yul
ScopeFiller.h
Utilities.cpp
Utilities.h
YulControlFlowGraphExporter.h
YulControlFlowGraphExporter.cpp
YulName.h
YulString.h
backends/evm/AbstractAssembly.h
Expand Down
212 changes: 212 additions & 0 deletions libyul/YulControlFlowGraphExporter.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
/*
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 <http://www.gnu.org/licenses/>.
*/
// SPDX-License-Identifier: GPL-3.0

#include <libyul/Utilities.h>
#include <libyul/YulControlFlowGraphExporter.h>

#include <libsolutil/Algorithms.h>
#include <libsolutil/Numeric.h>

#include <range/v3/view/enumerate.hpp>
#include <range/v3/view/map.hpp>
#include <range/v3/view/transform.hpp>

using namespace solidity;
using namespace solidity::langutil;
using namespace solidity::util;
using namespace solidity::yul;

YulControlFlowGraphExporter::YulControlFlowGraphExporter(ControlFlow const& _controlFlow): m_controlFlow(_controlFlow)
{
}

std::string YulControlFlowGraphExporter::varToString(SSACFG const& _cfg, SSACFG::ValueId _var)
{
if (_var.value == std::numeric_limits<size_t>::max())
return std::string("INVALID");
auto const& info = _cfg.valueInfo(_var);
return std::visit(
util::GenericVisitor{
[&](SSACFG::UnreachableValue const&) -> std::string {
return "[unreachable]";
},
[&](SSACFG::LiteralValue const& _literal) {
return toCompactHexWithPrefix(_literal.value);
},
[&](auto const&) {
return "v" + std::to_string(_var.value);
}
},
info
);
}

Json YulControlFlowGraphExporter::run()
{
Json yulObjectJson = Json::object();
yulObjectJson["blocks"] = exportBlock(*m_controlFlow.mainGraph, SSACFG::BlockId{0});

Json functionsJson = Json::object();
for (auto const& [function, functionGraph]: m_controlFlow.functionGraphMapping)
functionsJson[function->name.str()] = exportFunction(*functionGraph);
yulObjectJson["functions"] = functionsJson;

return yulObjectJson;
}

Json YulControlFlowGraphExporter::exportFunction(SSACFG const& _cfg)
{
Json functionJson = Json::object();
functionJson["type"] = "Function";
functionJson["entry"] = "Block" + std::to_string(_cfg.entry.value);
functionJson["arguments"] = Json::array();
for (auto const& [arg, valueId]: _cfg.arguments)
functionJson["arguments"].emplace_back(arg.get().name.str());
functionJson["returns"] = Json::array();
for (auto const& ret: _cfg.returns)
functionJson["returns"].emplace_back(ret.get().name.str());
functionJson["blocks"] = exportBlock(_cfg, _cfg.entry);
return functionJson;
}

Json YulControlFlowGraphExporter::exportBlock(SSACFG const& _cfg, SSACFG::BlockId _entryId)
{
Json blocksJson = Json::array();
util::BreadthFirstSearch<SSACFG::BlockId> bfs{{{_entryId}}};
bfs.run([&](SSACFG::BlockId _blockId, auto _addChild) {
auto const& block = _cfg.block(_blockId);
// Convert current block to JSON
Json blockJson = toJson(_cfg, _blockId);

Json exitBlockJson = Json::object();
std::visit(util::GenericVisitor{
[&](SSACFG::BasicBlock::MainExit const&) {
exitBlockJson["targets"] = { "Block" + std::to_string(_blockId.value) };
exitBlockJson["type"] = "MainExit";
},
[&](SSACFG::BasicBlock::Jump const& _jump)
{
exitBlockJson["targets"] = { "Block" + std::to_string(_jump.target.value) };
exitBlockJson["type"] = "Jump";
_addChild(_jump.target);
},
[&](SSACFG::BasicBlock::ConditionalJump const& _conditionalJump)
{
exitBlockJson["targets"] = { "Block" + std::to_string(_conditionalJump.zero.value), "Block" + std::to_string(_conditionalJump.nonZero.value) };
exitBlockJson["cond"] = varToString(_cfg, _conditionalJump.condition);
exitBlockJson["type"] = "ConditionalJump";

_addChild(_conditionalJump.zero);
_addChild(_conditionalJump.nonZero);
},
[&](SSACFG::BasicBlock::FunctionReturn const& _return) {
exitBlockJson["instructions"] = toJson(_cfg, _return.returnValues);
exitBlockJson["targets"] = { "Block" + std::to_string(_blockId.value) };
exitBlockJson["type"] = "FunctionReturn";
},
[&](SSACFG::BasicBlock::Terminated const&) {
exitBlockJson["targets"] = { "Block" + std::to_string(_blockId.value) };
exitBlockJson["type"] = "Terminated";
},
[&](SSACFG::BasicBlock::JumpTable const&) {
yulAssert(false);
}
}, block.exit);
blockJson["exit"] = exitBlockJson;
blocksJson.emplace_back(blockJson);
});

return blocksJson;
}

Json YulControlFlowGraphExporter::toJson(SSACFG const& _cfg, SSACFG::BlockId _blockId)
{
Json blockJson = Json::object();
auto const& block = _cfg.block(_blockId);

blockJson["id"] = "Block" + std::to_string(_blockId.value);
blockJson["instructions"] = Json::array();
if (!block.phis.empty())
{
blockJson["entries"] = block.entries
| ranges::views::transform([](auto const& entry) { return "Block" + std::to_string(entry.value); })
| ranges::to<Json::array_t>();
for (auto const& phi: block.phis)
{
auto* phiInfo = std::get_if<SSACFG::PhiValue>(&_cfg.valueInfo(phi));
yulAssert(phiInfo);
Json phiJson = Json::object();
phiJson["op"] = "PhiFunction";
phiJson["in"] = toJson(_cfg, phiInfo->arguments);
phiJson["out"] = toJson(_cfg, std::vector<SSACFG::ValueId>{phi});
blockJson["instructions"].push_back(phiJson);
}
}
for (auto const& operation: block.operations)
blockJson["instructions"].push_back(toJson(blockJson, _cfg, operation));

return blockJson;
}

Json YulControlFlowGraphExporter::toJson(Json& _ret, SSACFG const& _cfg, SSACFG::Operation const& _operation)
{
Json opJson = Json::object();
std::visit(util::GenericVisitor{
[&](SSACFG::Call const& _call) {
_ret["type"] = "FunctionCall";
opJson["op"] = _call.function.get().name.str();
},
[&](SSACFG::BuiltinCall const& _call) {
_ret["type"] = "BuiltinCall";
Json builtinArgsJson = Json::array();
auto const& builtin = _call.builtin.get();
if (!builtin.literalArguments.empty())
{
auto const& functionCallArgs = _call.call.get().arguments;
for (size_t i = 0; i < builtin.literalArguments.size(); ++i)
{
std::optional<LiteralKind> const& argument = builtin.literalArguments[i];
if (argument.has_value() && i < functionCallArgs.size())
{
// The function call argument at index i must be a literal if builtin.literalArguments[i] is not nullopt
yulAssert(std::holds_alternative<Literal>(functionCallArgs[i]));
builtinArgsJson.push_back(formatLiteral(std::get<Literal>(functionCallArgs[i])));
}
}
}

if (!builtinArgsJson.empty())
opJson["builtinArgs"] = builtinArgsJson;

opJson["op"] = _call.builtin.get().name.str();
},
}, _operation.kind);

opJson["in"] = toJson(_cfg, _operation.inputs);
opJson["out"] = toJson(_cfg, _operation.outputs);

return opJson;
}

Json YulControlFlowGraphExporter::toJson(SSACFG const& _cfg, std::vector<SSACFG::ValueId> const& _values)
{
Json ret = Json::array();
for (auto const& value: _values)
ret.push_back(varToString(_cfg, value));
return ret;
}
42 changes: 42 additions & 0 deletions libyul/YulControlFlowGraphExporter.h
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.
*/
// SPDX-License-Identifier: GPL-3.0

#pragma once

#include <libyul/backends/evm/ControlFlow.h>
#include <libsolutil/JSON.h>
#include <libsolutil/Visitor.h>

using namespace solidity;
using namespace yul;

class YulControlFlowGraphExporter
{
public:
YulControlFlowGraphExporter(ControlFlow const& _controlFlow);
Json run();
Json exportBlock(SSACFG const& _cfg, SSACFG::BlockId _blockId);
Json exportFunction(SSACFG const& _cfg);
std::string varToString(SSACFG const& _cfg, SSACFG::ValueId _var);

private:
ControlFlow const& m_controlFlow;
Json toJson(SSACFG const& _cfg, SSACFG::BlockId _blockId);
Json toJson(Json& _ret, SSACFG const& _cfg, SSACFG::Operation const& _operation);
Json toJson(SSACFG const& _cfg, std::vector<SSACFG::ValueId> const& _values);
};
Loading