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 --asm-json output in assembler mode #14612

Merged
merged 1 commit into from
Jul 31, 2024
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
1 change: 1 addition & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ Compiler Features:
* SMTChecker: Replace CVC4 as a possible BMC backend with cvc5.
* Standard JSON Interface: Do not perform IR optimization when only unoptimized IR is requested.
* Yul Optimizer: The optimizer now treats some previously unrecognized identical literals as identical.
* Commandline Interface: Allow the use of ``--asm-json`` output option in assembler mode to export EVM assembly of the contracts in JSON format.
aarlt marked this conversation as resolved.
Show resolved Hide resolved


Bugfixes:
Expand Down
23 changes: 20 additions & 3 deletions libevmasm/Assembly.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -479,8 +479,18 @@ Json Assembly::assemblyJSON(std::map<std::string, unsigned> const& _sourceIndice
{
root["sourceList"] = Json::array();
Json& jsonSourceList = root["sourceList"];
for (auto const& [name, index]: _sourceIndices)
jsonSourceList[index] = name;
unsigned maxSourceIndex = 0;
for (auto const& [sourceName, sourceIndex]: _sourceIndices)
{
maxSourceIndex = std::max(sourceIndex, maxSourceIndex);
jsonSourceList[sourceIndex] = sourceName;
}
solAssert(maxSourceIndex + 1 >= _sourceIndices.size());
solRequire(
_sourceIndices.size() == 0 || _sourceIndices.size() == maxSourceIndex + 1,
AssemblyImportException,
"The 'sourceList' array contains invalid 'null' item."
);
}

if (!m_data.empty() || !m_subs.empty())
Expand Down Expand Up @@ -522,7 +532,14 @@ std::pair<std::shared_ptr<Assembly>, std::vector<std::string>> Assembly::fromJSO
{
solRequire(_json["sourceList"].is_array(), AssemblyImportException, "Optional member 'sourceList' is not an array.");
for (Json const& sourceName: _json["sourceList"])
solRequire(sourceName.is_string(), AssemblyImportException, "The 'sourceList' array contains an item that is not a string.");
{
solRequire(!sourceName.is_null(), AssemblyImportException, "The 'sourceList' array contains invalid 'null' item.");
solRequire(
sourceName.is_string(),
AssemblyImportException,
"The 'sourceList' array contains an item that is not a string."
);
}
}
}
else
Expand Down
2 changes: 1 addition & 1 deletion libsolidity/interface/StandardCompiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1694,7 +1694,7 @@ Json StandardCompiler::compileYul(InputsAndSettings _inputsAndSettings)
if (isArtifactRequested(_inputsAndSettings.outputSelection, sourceName, contractName, "irOptimized", wildcardMatchesExperimental))
output["contracts"][sourceName][contractName]["irOptimized"] = stack.print();
if (isArtifactRequested(_inputsAndSettings.outputSelection, sourceName, contractName, "evm.assembly", wildcardMatchesExperimental))
output["contracts"][sourceName][contractName]["evm"]["assembly"] = object.assembly;
output["contracts"][sourceName][contractName]["evm"]["assembly"] = object.assembly->assemblyString(stack.debugInfoSelection());

return output;
}
Expand Down
30 changes: 30 additions & 0 deletions libyul/Object.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -160,3 +160,33 @@ std::vector<size_t> Object::pathToSubObject(std::string_view _qualifiedName) con

return path;
}

void Object::collectSourceIndices(std::map<std::string, unsigned>& _indices) const
{
if (debugData && debugData->sourceNames.has_value())
for (auto const& [sourceIndex, sourceName]: debugData->sourceNames.value())
{
solAssert(_indices.count(*sourceName) == 0 || _indices[*sourceName] == sourceIndex);
_indices[*sourceName] = sourceIndex;
}
for (std::shared_ptr<ObjectNode> const& subNode: subObjects)
if (auto subObject = dynamic_cast<Object*>(subNode.get()))
subObject->collectSourceIndices(_indices);
}

bool Object::hasContiguousSourceIndices() const
{
std::map<std::string, unsigned> sourceIndices;
collectSourceIndices(sourceIndices);

unsigned maxSourceIndex = 0;
std::set<unsigned> indices;
for (auto const& [sources, sourceIndex]: sourceIndices)
{
maxSourceIndex = std::max(sourceIndex, maxSourceIndex);
indices.insert(sourceIndex);
}

solAssert(maxSourceIndex + 1 >= indices.size());
return indices.size() == 0 || indices.size() == maxSourceIndex + 1;
}
8 changes: 8 additions & 0 deletions libyul/Object.h
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,14 @@ struct Object: public ObjectNode

std::shared_ptr<ObjectDebugData const> debugData;

/// Collects names of all Solidity source units present in the debug data
/// of the Yul object (including sub-objects) and their assigned indices.
/// @param _indices map that will be filled with source indices of the current Yul object & its sub-objects.
void collectSourceIndices(std::map<std::string, unsigned>& _indices) const;

/// @returns true, if the range of source indices starts at zero and is contiguous, false otherwise.
bool hasContiguousSourceIndices() const;

/// @returns the name of the special metadata data object.
static std::string metadataName() { return ".metadata"; }
};
Expand Down
9 changes: 2 additions & 7 deletions libyul/YulStack.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,6 @@
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
// SPDX-License-Identifier: GPL-3.0
/**
* Full assembly stack that can support EVM-assembly and Yul as input and EVM, EVM1.5
*/


#include <libyul/YulStack.h>

Expand Down Expand Up @@ -64,7 +60,6 @@ Dialect const& languageToDialect(YulStack::Language _language, EVMVersion _versi

}


CharStream const& YulStack::charStream(std::string const& _sourceName) const
{
yulAssert(m_charStream, "");
Expand Down Expand Up @@ -274,7 +269,7 @@ YulStack::assembleWithDeployed(std::optional<std::string_view> _deployName)
{
creationObject.bytecode = std::make_shared<evmasm::LinkerObject>(creationAssembly->assemble());
yulAssert(creationObject.bytecode->immutableReferences.empty(), "Leftover immutables.");
creationObject.assembly = creationAssembly->assemblyString(m_debugInfoSelection);
creationObject.assembly = creationAssembly;
creationObject.sourceMappings = std::make_unique<std::string>(
evmasm::AssemblyItem::computeSourceMapping(
creationAssembly->items(),
Expand All @@ -285,7 +280,7 @@ YulStack::assembleWithDeployed(std::optional<std::string_view> _deployName)
if (deployedAssembly)
{
deployedObject.bytecode = std::make_shared<evmasm::LinkerObject>(deployedAssembly->assemble());
deployedObject.assembly = deployedAssembly->assemblyString(m_debugInfoSelection);
deployedObject.assembly = deployedAssembly;
deployedObject.sourceMappings = std::make_unique<std::string>(
evmasm::AssemblyItem::computeSourceMapping(
deployedAssembly->items(),
Expand Down
7 changes: 5 additions & 2 deletions libyul/YulStack.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
*/
// SPDX-License-Identifier: GPL-3.0
/**
* Full assembly stack that can support EVM-assembly and Yul as input and EVM.
* Full assembly stack that can support Yul as input.
*/

#pragma once
Expand Down Expand Up @@ -55,7 +55,7 @@ class AbstractAssembly;
struct MachineAssemblyObject
{
std::shared_ptr<evmasm::LinkerObject> bytecode;
std::string assembly;
std::shared_ptr<evmasm::Assembly> assembly;
std::unique_ptr<std::string> sourceMappings;
};

Expand Down Expand Up @@ -137,9 +137,12 @@ class YulStack: public langutil::CharStreamProvider
langutil::CharStreamProvider const* _soliditySourceProvider = nullptr
) const;
Json astJson() const;

/// Return the parsed and analyzed object.
std::shared_ptr<Object> parserResult() const;

langutil::DebugInfoSelection debugInfoSelection() const { return m_debugInfoSelection; }

private:
bool parse(std::string const& _sourceName, std::string const& _source);
bool analyzeParsed();
Expand Down
28 changes: 25 additions & 3 deletions solc/CommandLineInterface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ void CommandLineInterface::handleEVMAssembly(std::string const& _contract)

std::string assembly;
if (m_options.compiler.outputs.asmJson)
assembly = util::jsonPrint(removeNullMembers(m_assemblyStack->assemblyJSON(_contract)), m_options.formatting.json);
assembly = util::jsonPrint(m_assemblyStack->assemblyJSON(_contract), m_options.formatting.json);
else
assembly = m_assemblyStack->assemblyString(_contract, m_fileReader.sourceUnits());

Expand Down Expand Up @@ -1197,6 +1197,17 @@ void CommandLineInterface::assembleYul(yul::YulStack::Language _language, yul::Y
successful = false;
else
stack.optimize();

if (successful && m_options.compiler.outputs.asmJson)
{
std::shared_ptr<yul::Object> result = stack.parserResult();
if (result && !result->hasContiguousSourceIndices())
solThrow(
CommandLineExecutionError,
"Generating the assembly JSON output was not possible. "
"Source indices provided in the @use-src annotation in the Yul input do not start at 0 or are not contiguous."
);
}
}

for (auto const& sourceAndStack: yulStacks)
Expand Down Expand Up @@ -1256,11 +1267,22 @@ void CommandLineInterface::assembleYul(yul::YulStack::Language _language, yul::Y
if (m_options.compiler.outputs.asm_)
{
sout() << std::endl << "Text representation:" << std::endl;
if (!object.assembly.empty())
sout() << object.assembly << std::endl;
std::string assemblyText{object.assembly->assemblyString(stack.debugInfoSelection())};
if (!assemblyText.empty())
sout() << assemblyText << std::endl;
else
report(Error::Severity::Info, "No text representation found.");
}
if (m_options.compiler.outputs.asmJson)
{
sout() << std::endl << "EVM assembly:" << std::endl;
std::map<std::string, unsigned> sourceIndices;
stack.parserResult()->collectSourceIndices(sourceIndices);
sout() << util::jsonPrint(
object.assembly->assemblyJSON(sourceIndices),
m_options.formatting.json
) << std::endl;
}
}
}

Expand Down
1 change: 1 addition & 0 deletions solc/CommandLineParser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -472,6 +472,7 @@ void CommandLineParser::parseOutputSelection()
CompilerOutputs::componentName(&CompilerOutputs::binary),
CompilerOutputs::componentName(&CompilerOutputs::irOptimized),
CompilerOutputs::componentName(&CompilerOutputs::astCompactJson),
CompilerOutputs::componentName(&CompilerOutputs::asmJson),
};
static std::set<std::string> const evmAssemblyJsonImportModeOutputs = {
CompilerOutputs::componentName(&CompilerOutputs::asm_),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
--pretty-json --json-indent 4 --import-asm-json -
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Error: Assembly Import Error: The 'sourceList' array contains invalid 'null' item.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
1
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
".code":
[
{
"begin": -1,
"end": -1,
"name": "STOP",
"source": -1
}
],
"sourceList":
[
null,
null,
"L0.sol"
]
}
1 change: 1 addition & 0 deletions test/cmdlineTests/asm_json_yul_export_evm_asm_import/args
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
--import-asm-json - --opcodes --asm --bin --asm-json --pretty-json --json-indent 4
cameel marked this conversation as resolved.
Show resolved Hide resolved
39 changes: 39 additions & 0 deletions test/cmdlineTests/asm_json_yul_export_evm_asm_import/output
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
Opcodes:
PUSH1 0x5 PUSH0 SSTORE STOP
Binary:
60055f5500
EVM assembly:
{
".code": [
{
"begin": -1,
"end": -1,
"name": "PUSHSIZE",
"source": -1
},
{
"begin": -1,
"end": -1,
"name": "PUSH",
"source": -1,
"value": "0"
},
{
"begin": -1,
"end": -1,
"name": "SSTORE",
"source": -1
},
{
"begin": -1,
"end": -1,
"name": "STOP",
"source": -1
}
],
"sourceList": [
"L0.sol",
"L1.sol",
"L2.sol"
]
}
36 changes: 36 additions & 0 deletions test/cmdlineTests/asm_json_yul_export_evm_asm_import/stdin
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
".code":
[
{
"begin": -1,
"end": -1,
"name": "PUSHSIZE",
"source": -1
},
{
"begin": -1,
"end": -1,
"name": "PUSH",
"source": -1,
"value": "0"
},
{
"begin": -1,
"end": -1,
"name": "SSTORE",
"source": -1
},
{
"begin": -1,
"end": -1,
"name": "STOP",
"source": -1
}
],
"sourceList":
[
"L0.sol",
"L1.sol",
"L2.sol"
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
--strict-assembly - --asm-json --pretty-json --json-indent 4
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Error: Generating the assembly JSON output was not possible. Source indices provided in the @use-src annotation in the Yul input do not start at 0 or are not contiguous.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
1
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/// @use-src 2:"L0.sol"
object "L0" {
code {
sstore(0, datasize("L0"))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
--strict-assembly - --asm-json --pretty-json --json-indent 4
Loading