Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for Yul ASTs output #14177

Merged
merged 1 commit into from
May 26, 2023
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
4 changes: 4 additions & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,12 @@ Language Features:


Compiler Features:
* Commandline Interface: Add ``--ast-compact-json`` output in assembler mode.
* Commandline Interface: Add ``--ir-ast-json`` and ``--ir-optimized-ast-json`` outputs for Solidity input, providing AST in compact JSON format for IR and optimized IR.
* EWasm: Remove EWasm backend.
* Parser: Introduce ``pragma experimental solidity``, which will enable an experimental language mode that in particular has no stability guarantees between non-breaking releases and is not suited for production use.
* Standard JSON Interface: Add ``ast`` file-level output for Yul input.
* Standard JSON Interface: Add ``irAst`` and ``irOptimizedAst`` contract-level outputs for Solidity input, providing AST in compact JSON format for IR and optimized IR.


Bugfixes:
Expand Down
10 changes: 9 additions & 1 deletion docs/using-the-compiler.rst
Original file line number Diff line number Diff line change
Expand Up @@ -390,7 +390,9 @@ Input Description
// userdoc - User documentation (natspec)
// metadata - Metadata
// ir - Yul intermediate representation of the code before optimization
// irAst - AST of Yul intermediate representation of the code before optimization
// irOptimized - Intermediate representation after optimization
// irOptimizedAst - AST of intermediate representation after optimization
// storageLayout - Slots, offsets and types of the contract's state variables.
// evm.assembly - New assembly format
// evm.legacyAssembly - Old-style assembly format in JSON
Expand Down Expand Up @@ -536,8 +538,14 @@ Output Description
"userdoc": {},
// Developer documentation (natspec)
"devdoc": {},
// Intermediate representation (string)
// Intermediate representation before optimization (string)
"ir": "",
// AST of intermediate representation before optimization
"irAst": {/* ... */},
// Intermediate representation after optimization (string)
"irOptimized": "",
// AST of intermediate representation after optimization
"irOptimizedAst": {/* ... */},
// See the Storage Layout documentation.
"storageLayout": {"storage": [/* ... */], "types": {/* ... */} },
// EVM-related outputs
Expand Down
8 changes: 6 additions & 2 deletions libsolidity/codegen/ir/IRGenerator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@

#include <liblangutil/SourceReferenceFormatter.h>

#include <json/json.h>

#include <sstream>
#include <variant>

Expand Down Expand Up @@ -87,7 +89,7 @@ set<CallableDeclaration const*, ASTNode::CompareByID> collectReachableCallables(

}

pair<string, string> IRGenerator::run(
tuple<string, Json::Value, string, Json::Value> IRGenerator::run(
ContractDefinition const& _contract,
bytes const& _cborMetadata,
map<ContractDefinition const*, string_view const> const& _otherYulSources
Expand All @@ -112,9 +114,11 @@ pair<string, string> IRGenerator::run(
);
solAssert(false, ir + "\n\nInvalid IR generated:\n" + errorMessage + "\n");
}
Json::Value irAst = asmStack.astJson();
asmStack.optimize();
Json::Value irOptAst = asmStack.astJson();

return {std::move(ir), asmStack.print(m_context.soliditySourceProvider())};
return {std::move(ir), std::move(irAst), asmStack.print(m_context.soliditySourceProvider()), std::move(irOptAst)};
}

string IRGenerator::generate(
Expand Down
4 changes: 3 additions & 1 deletion libsolidity/codegen/ir/IRGenerator.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@
#include <liblangutil/CharStreamProvider.h>
#include <liblangutil/EVMVersion.h>

#include <json/json.h>

#include <string>

namespace solidity::frontend
Expand Down Expand Up @@ -70,7 +72,7 @@ class IRGenerator

/// Generates and returns the IR code, in unoptimized and optimized form
/// (or just pretty-printed, depending on the optimizer settings).
std::pair<std::string, std::string> run(
std::tuple<std::string, Json::Value, std::string, Json::Value> run(
ContractDefinition const& _contract,
bytes const& _cborMetadata,
std::map<ContractDefinition const*, std::string_view const> const& _otherYulSources
Expand Down
24 changes: 23 additions & 1 deletion libsolidity/interface/CompilerStack.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -883,6 +883,14 @@ string const& CompilerStack::yulIR(string const& _contractName) const
return contract(_contractName).yulIR;
}

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

return contract(_contractName).yulIRAst;
}

string const& CompilerStack::yulIROptimized(string const& _contractName) const
{
if (m_stackState != CompilationSuccessful)
Expand All @@ -891,6 +899,14 @@ string const& CompilerStack::yulIROptimized(string const& _contractName) const
return contract(_contractName).yulIROptimized;
}

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

return contract(_contractName).yulIROptimizedAst;
}

evmasm::LinkerObject const& CompilerStack::object(string const& _contractName) const
{
if (m_stackState != CompilationSuccessful)
Expand Down Expand Up @@ -1445,7 +1461,13 @@ void CompilerStack::generateIR(ContractDefinition const& _contract)
m_debugInfoSelection,
this
);
tie(compiledContract.yulIR, compiledContract.yulIROptimized) = generator.run(

tie(
compiledContract.yulIR,
compiledContract.yulIRAst,
compiledContract.yulIROptimized,
compiledContract.yulIROptimizedAst
) = generator.run(
_contract,
createCBORMetadata(compiledContract, /* _forIR */ true),
otherYulSources
Expand Down
8 changes: 8 additions & 0 deletions libsolidity/interface/CompilerStack.h
Original file line number Diff line number Diff line change
Expand Up @@ -276,9 +276,15 @@ class CompilerStack: public langutil::CharStreamProvider
/// @returns the IR representation of a contract.
std::string const& yulIR(std::string const& _contractName) const;

/// @returns the IR representation of a contract AST in format.
Json::Value const& yulIRAst(std::string const& _contractName) const;

/// @returns the optimized IR representation of a contract.
std::string const& yulIROptimized(std::string const& _contractName) const;

/// @returns the optimized IR representation of a contract AST in JSON format.
Json::Value const& yulIROptimizedAst(std::string const& _contractName) const;

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

Expand Down Expand Up @@ -380,6 +386,8 @@ class CompilerStack: public langutil::CharStreamProvider
evmasm::LinkerObject runtimeObject; ///< Runtime object.
std::string yulIR; ///< Yul IR code.
std::string yulIROptimized; ///< Optimized Yul IR code.
Json::Value yulIRAst; ///< JSON AST of Yul IR code.
Json::Value yulIROptimizedAst; ///< JSON AST of optimized Yul IR code.
util::LazyInit<std::string const> metadata; ///< The metadata json that will be hashed into the chain.
util::LazyInit<Json::Value const> abi;
util::LazyInit<Json::Value const> storageLayout;
Expand Down
24 changes: 20 additions & 4 deletions libsolidity/interface/StandardCompiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ bool hashMatchesContent(string const& _hash, string const& _content)

bool isArtifactRequested(Json::Value const& _outputSelection, string const& _artifact, bool _wildcardMatchesExperimental)
{
static set<string> experimental{"ir", "irOptimized"};
static set<string> experimental{"ir", "irAst", "irOptimized", "irOptimizedAst"};
for (auto const& selectedArtifactJson: _outputSelection)
{
string const& selectedArtifact = selectedArtifactJson.asString();
Expand Down Expand Up @@ -263,7 +263,7 @@ bool isBinaryRequested(Json::Value const& _outputSelection)
// This does not include "evm.methodIdentifiers" on purpose!
static vector<string> const outputsThatRequireBinaries = vector<string>{
"*",
"ir", "irOptimized",
"ir", "irAst", "irOptimized", "irOptimizedAst",
"evm.gasEstimates", "evm.legacyAssembly", "evm.assembly"
} + evmObjectComponents("bytecode") + evmObjectComponents("deployedBytecode");

Expand Down Expand Up @@ -295,7 +295,7 @@ bool isEvmBytecodeRequested(Json::Value const& _outputSelection)
}

/// @returns true if any Yul IR was requested. Note that as an exception, '*' does not
/// yet match "ir" or "irOptimized"
/// yet match "ir", "irAst", "irOptimized" or "irOptimizedAst"
bool isIRRequested(Json::Value const& _outputSelection)
{
if (!_outputSelection.isObject())
Expand All @@ -304,7 +304,12 @@ bool isIRRequested(Json::Value const& _outputSelection)
for (auto const& fileRequests: _outputSelection)
for (auto const& requests: fileRequests)
for (auto const& request: requests)
if (request == "ir" || request == "irOptimized")
if (
request == "ir" ||
request == "irAst" ||
request == "irOptimized" ||
request == "irOptimizedAst"
)
return true;

return false;
Expand Down Expand Up @@ -1350,8 +1355,12 @@ Json::Value StandardCompiler::compileSolidity(StandardCompiler::InputsAndSetting
// IR
if (compilationSuccess && isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "ir", wildcardMatchesExperimental))
contractData["ir"] = compilerStack.yulIR(contractName);
if (compilationSuccess && isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "irAst", wildcardMatchesExperimental))
contractData["irAst"] = compilerStack.yulIRAst(contractName);
if (compilationSuccess && isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "irOptimized", wildcardMatchesExperimental))
contractData["irOptimized"] = compilerStack.yulIROptimized(contractName);
if (compilationSuccess && isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "irOptimizedAst", wildcardMatchesExperimental))
contractData["irOptimizedAst"] = compilerStack.yulIROptimizedAst(contractName);

// EVM
Json::Value evmData(Json::objectValue);
Expand Down Expand Up @@ -1513,6 +1522,13 @@ Json::Value StandardCompiler::compileYul(InputsAndSettings _inputsAndSettings)
if (isArtifactRequested(_inputsAndSettings.outputSelection, sourceName, contractName, "ir", wildcardMatchesExperimental))
output["contracts"][sourceName][contractName]["ir"] = stack.print();

if (isArtifactRequested(_inputsAndSettings.outputSelection, sourceName, contractName, "ast", wildcardMatchesExperimental))
{
Json::Value sourceResult = Json::objectValue;
sourceResult["id"] = 1;
sourceResult["ast"] = stack.astJson();
output["sources"][sourceName] = sourceResult;
}
stack.optimize();

MachineAssemblyObject object;
Expand Down
45 changes: 24 additions & 21 deletions libyul/AsmJsonConverter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,23 +33,23 @@ namespace solidity::yul

Json::Value AsmJsonConverter::operator()(Block const& _node) const
{
Json::Value ret = createAstNode(nativeLocationOf(_node), "YulBlock");
Json::Value ret = createAstNode(originLocationOf(_node), nativeLocationOf(_node), "YulBlock");
ret["statements"] = vectorOfVariantsToJson(_node.statements);
return ret;
}

Json::Value AsmJsonConverter::operator()(TypedName const& _node) const
{
yulAssert(!_node.name.empty(), "Invalid variable name.");
Json::Value ret = createAstNode(nativeLocationOf(_node), "YulTypedName");
Json::Value ret = createAstNode(originLocationOf(_node), nativeLocationOf(_node), "YulTypedName");
ret["name"] = _node.name.str();
ret["type"] = _node.type.str();
return ret;
}

Json::Value AsmJsonConverter::operator()(Literal const& _node) const
{
Json::Value ret = createAstNode(nativeLocationOf(_node), "YulLiteral");
Json::Value ret = createAstNode(originLocationOf(_node), nativeLocationOf(_node), "YulLiteral");
switch (_node.kind)
{
case LiteralKind::Number:
Expand All @@ -76,15 +76,15 @@ Json::Value AsmJsonConverter::operator()(Literal const& _node) const
Json::Value AsmJsonConverter::operator()(Identifier const& _node) const
{
yulAssert(!_node.name.empty(), "Invalid identifier");
Json::Value ret = createAstNode(nativeLocationOf(_node), "YulIdentifier");
Json::Value ret = createAstNode(originLocationOf(_node), nativeLocationOf(_node), "YulIdentifier");
ret["name"] = _node.name.str();
return ret;
}

Json::Value AsmJsonConverter::operator()(Assignment const& _node) const
{
yulAssert(_node.variableNames.size() >= 1, "Invalid assignment syntax");
Json::Value ret = createAstNode(nativeLocationOf(_node), "YulAssignment");
Json::Value ret = createAstNode(originLocationOf(_node), nativeLocationOf(_node), "YulAssignment");
for (auto const& var: _node.variableNames)
ret["variableNames"].append((*this)(var));
ret["value"] = _node.value ? std::visit(*this, *_node.value) : Json::nullValue;
Expand All @@ -93,22 +93,22 @@ Json::Value AsmJsonConverter::operator()(Assignment const& _node) const

Json::Value AsmJsonConverter::operator()(FunctionCall const& _node) const
{
Json::Value ret = createAstNode(nativeLocationOf(_node), "YulFunctionCall");
Json::Value ret = createAstNode(originLocationOf(_node), nativeLocationOf(_node), "YulFunctionCall");
ret["functionName"] = (*this)(_node.functionName);
ret["arguments"] = vectorOfVariantsToJson(_node.arguments);
return ret;
}

Json::Value AsmJsonConverter::operator()(ExpressionStatement const& _node) const
{
Json::Value ret = createAstNode(nativeLocationOf(_node), "YulExpressionStatement");
Json::Value ret = createAstNode(originLocationOf(_node), nativeLocationOf(_node), "YulExpressionStatement");
ret["expression"] = std::visit(*this, _node.expression);
return ret;
}

Json::Value AsmJsonConverter::operator()(VariableDeclaration const& _node) const
{
Json::Value ret = createAstNode(nativeLocationOf(_node), "YulVariableDeclaration");
Json::Value ret = createAstNode(originLocationOf(_node), nativeLocationOf(_node), "YulVariableDeclaration");
for (auto const& var: _node.variables)
ret["variables"].append((*this)(var));

Expand All @@ -120,7 +120,7 @@ Json::Value AsmJsonConverter::operator()(VariableDeclaration const& _node) const
Json::Value AsmJsonConverter::operator()(FunctionDefinition const& _node) const
{
yulAssert(!_node.name.empty(), "Invalid function name.");
Json::Value ret = createAstNode(nativeLocationOf(_node), "YulFunctionDefinition");
Json::Value ret = createAstNode(originLocationOf(_node), nativeLocationOf(_node), "YulFunctionDefinition");
ret["name"] = _node.name.str();
for (auto const& var: _node.parameters)
ret["parameters"].append((*this)(var));
Expand All @@ -133,7 +133,7 @@ Json::Value AsmJsonConverter::operator()(FunctionDefinition const& _node) const
Json::Value AsmJsonConverter::operator()(If const& _node) const
{
yulAssert(_node.condition, "Invalid if condition.");
Json::Value ret = createAstNode(nativeLocationOf(_node), "YulIf");
Json::Value ret = createAstNode(originLocationOf(_node), nativeLocationOf(_node), "YulIf");
ret["condition"] = std::visit(*this, *_node.condition);
ret["body"] = (*this)(_node.body);
return ret;
Expand All @@ -142,7 +142,7 @@ Json::Value AsmJsonConverter::operator()(If const& _node) const
Json::Value AsmJsonConverter::operator()(Switch const& _node) const
{
yulAssert(_node.expression, "Invalid expression pointer.");
Json::Value ret = createAstNode(nativeLocationOf(_node), "YulSwitch");
Json::Value ret = createAstNode(originLocationOf(_node), nativeLocationOf(_node), "YulSwitch");
ret["expression"] = std::visit(*this, *_node.expression);
for (auto const& var: _node.cases)
ret["cases"].append((*this)(var));
Expand All @@ -151,7 +151,7 @@ Json::Value AsmJsonConverter::operator()(Switch const& _node) const

Json::Value AsmJsonConverter::operator()(Case const& _node) const
{
Json::Value ret = createAstNode(nativeLocationOf(_node), "YulCase");
Json::Value ret = createAstNode(originLocationOf(_node), nativeLocationOf(_node), "YulCase");
ret["value"] = _node.value ? (*this)(*_node.value) : "default";
ret["body"] = (*this)(_node.body);
return ret;
Expand All @@ -160,7 +160,7 @@ Json::Value AsmJsonConverter::operator()(Case const& _node) const
Json::Value AsmJsonConverter::operator()(ForLoop const& _node) const
{
yulAssert(_node.condition, "Invalid for loop condition.");
Json::Value ret = createAstNode(nativeLocationOf(_node), "YulForLoop");
Json::Value ret = createAstNode(originLocationOf(_node), nativeLocationOf(_node), "YulForLoop");
ret["pre"] = (*this)(_node.pre);
ret["condition"] = std::visit(*this, *_node.condition);
ret["post"] = (*this)(_node.post);
Expand All @@ -170,27 +170,30 @@ Json::Value AsmJsonConverter::operator()(ForLoop const& _node) const

Json::Value AsmJsonConverter::operator()(Break const& _node) const
{
return createAstNode(nativeLocationOf(_node), "YulBreak");
return createAstNode(originLocationOf(_node), nativeLocationOf(_node), "YulBreak");
}

Json::Value AsmJsonConverter::operator()(Continue const& _node) const
{
return createAstNode(nativeLocationOf(_node), "YulContinue");
return createAstNode(originLocationOf(_node), nativeLocationOf(_node), "YulContinue");
}

Json::Value AsmJsonConverter::operator()(Leave const& _node) const
{
return createAstNode(nativeLocationOf(_node), "YulLeave");
return createAstNode(originLocationOf(_node), nativeLocationOf(_node), "YulLeave");
}

Json::Value AsmJsonConverter::createAstNode(langutil::SourceLocation const& _location, string _nodeType) const
Json::Value AsmJsonConverter::createAstNode(langutil::SourceLocation const& _originLocation, langutil::SourceLocation const& _nativeLocation, string _nodeType) const
{
Json::Value ret{Json::objectValue};
ret["nodeType"] = std::move(_nodeType);
int length = -1;
if (_location.start >= 0 && _location.end >= 0)
length = _location.end - _location.start;
ret["src"] = to_string(_location.start) + ":" + to_string(length) + ":" + (m_sourceIndex.has_value() ? to_string(m_sourceIndex.value()) : "-1");
auto srcLocation = [&](int start, int end) -> string
{
int length = (start >= 0 && end >= 0 && end >= start) ? end - start : -1;
return to_string(start) + ":" + to_string(length) + ":" + (m_sourceIndex.has_value() ? to_string(m_sourceIndex.value()) : "-1");
};
ret["src"] = srcLocation(_originLocation.start, _originLocation.end);
ret["nativeSrc"] = srcLocation(_nativeLocation.start, _nativeLocation.end);
return ret;
}

Expand Down
2 changes: 1 addition & 1 deletion libyul/AsmJsonConverter.h
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ class AsmJsonConverter: public boost::static_visitor<Json::Value>
Json::Value operator()(Label const& _node) const;

private:
Json::Value createAstNode(langutil::SourceLocation const& _location, std::string _nodeType) const;
Json::Value createAstNode(langutil::SourceLocation const& _originLocation, langutil::SourceLocation const& _nativeLocation, std::string _nodeType) const;
template <class T>
Json::Value vectorOfVariantsToJson(std::vector<T> const& vec) const;

Expand Down
Loading