Skip to content

Commit

Permalink
Add --asm-json option in assembler mode.
Browse files Browse the repository at this point in the history
Co-authored-by: r0qs <rodrigo.saramago@ethereum.org>
Co-authored-by: Kamil Śliwak <kamil.sliwak@codepoets.it>
  • Loading branch information
3 people committed Jul 30, 2024
1 parent e0fb91f commit 905ac49
Show file tree
Hide file tree
Showing 36 changed files with 1,558 additions and 26 deletions.
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.


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
9 changes: 7 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 All @@ -163,6 +166,8 @@ class YulStack: public langutil::CharStreamProvider
std::shared_ptr<yul::Object> m_parserResult;
langutil::ErrorList m_errors;
langutil::ErrorReporter m_errorReporter;

std::unique_ptr<std::string> m_sourceMappings;
};

}
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
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

0 comments on commit 905ac49

Please sign in to comment.