Skip to content

Commit

Permalink
Convert Yul CFG to Json
Browse files Browse the repository at this point in the history
  • Loading branch information
r0qs committed Aug 28, 2024
1 parent adeed47 commit d052708
Show file tree
Hide file tree
Showing 13 changed files with 362 additions and 5 deletions.
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

0 comments on commit d052708

Please sign in to comment.