Skip to content

Add --import-asm-json input mode. #12704

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

Closed
wants to merge 10 commits into from
Closed
423 changes: 336 additions & 87 deletions libevmasm/Assembly.cpp

Large diffs are not rendered by default.

29 changes: 25 additions & 4 deletions libevmasm/Assembly.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
#include <sstream>
#include <memory>
#include <map>
#include <utility>

namespace solidity::evmasm
{
Expand Down Expand Up @@ -147,9 +148,12 @@ class Assembly

/// Create a JSON representation of the assembly.
Json::Value assemblyJSON(
std::map<std::string, unsigned> const& _sourceIndices = std::map<std::string, unsigned>()
std::map<std::string, unsigned> const& _sourceIndices = std::map<std::string, unsigned>(),
bool _includeSourceList = true
) const;

bool loadFromAssemblyJSON(Json::Value const& _json, bool _loadSources = true);

/// Mark this assembly as invalid. Calling ``assemble`` on it will throw.
void markAsInvalid() { m_invalid = true; }

Expand All @@ -158,6 +162,16 @@ class Assembly

bool isCreation() const { return m_creation; }

void setSources(std::vector<std::shared_ptr<std::string const>> _sources) {
m_sources = std::move(_sources);
}

void setSources(std::vector<std::string> const& _sources) {
for (auto const& item: _sources)
m_sources.emplace_back(std::make_shared<std::string>(item));
}
std::vector<std::shared_ptr<std::string const>> sources() const& { return m_sources; }

protected:
/// Does the same operations as @a optimise, but should only be applied to a sub and
/// returns the replaced tags. Also takes an argument containing the tags of this assembly
Expand All @@ -166,14 +180,19 @@ class Assembly

unsigned codeSize(unsigned subTagSize) const;

AssemblyItem loadItemFromJSON(Json::Value const& _json);
std::vector<Json::Value> assemblyItemAsJSON(AssemblyItem const& _item, int _sourceIndex) const;

private:
bool addAssemblyItemsFromJSON(Json::Value const& _code);
static Json::Value createJsonValue(
std::string _name,
int _source,
int _sourceIndex,
size_t _modifierDepth,
int _begin,
int _end,
std::string _value = std::string(),
std::string _jumpType = std::string()
std::string const& _value = std::string(),
std::string const& _jumpType = std::string()
);
static std::string toStringInHex(u256 _value);

Expand Down Expand Up @@ -222,6 +241,8 @@ class Assembly
std::string m_name;

langutil::SourceLocation m_currentSourceLocation;
std::vector<std::shared_ptr<std::string const>> m_sources;

public:
size_t m_currentModifierDepth = 0;
};
Expand Down
12 changes: 12 additions & 0 deletions libevmasm/AssemblyItem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,18 @@ string AssemblyItem::getJumpTypeAsString() const
}
}

void AssemblyItem::setJumpType(std::string const& _jumpType)
{
if (_jumpType == "[in]")
m_jumpType = JumpType::IntoFunction;
else if (_jumpType == "[out]")
m_jumpType = JumpType::OutOfFunction;
else if (_jumpType.empty())
m_jumpType = JumpType::Ordinary;
else
assertThrow(false, AssemblyException, "Invalid jump type.");
}

string AssemblyItem::toAssemblyText(Assembly const& _assembly) const
{
string text;
Expand Down
1 change: 1 addition & 0 deletions libevmasm/AssemblyItem.h
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ class AssemblyItem
langutil::SourceLocation const& location() const { return m_location; }

void setJumpType(JumpType _jumpType) { m_jumpType = _jumpType; }
void setJumpType(std::string const& _jumpType);
JumpType getJumpType() const { return m_jumpType; }
std::string getJumpTypeAsString() const;

Expand Down
156 changes: 109 additions & 47 deletions libsolidity/interface/CompilerStack.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,28 @@ void CompilerStack::importASTs(map<string, Json::Value> const& _sources)
storeContractDefinitions();
}

void CompilerStack::importEvmAssemblyJson(std::map<std::string, Json::Value> const& _sources)
{
solAssert(_sources.size() == 1, "");
solAssert(m_sources.empty(), "");
solAssert(m_sourceOrder.empty(), "");
if (m_stackState != Empty)
solThrow(CompilerError, "Must call importEvmAssemblyJson only before the SourcesSet state.");

Json::Value jsonValue = _sources.begin()->second;
if (jsonValue.isMember("sourceList"))
for (auto const& item: jsonValue["sourceList"])
{
Source source;
source.charStream = std::make_shared<CharStream>(item.asString(), "");
m_sources.emplace(std::make_pair(item.asString(), source));
m_sourceOrder.push_back(&m_sources[item.asString()]);
}
m_evmAssemblyJson[_sources.begin()->first] = jsonValue;
m_importedSources = true;
m_stackState = SourcesSet;
}

bool CompilerStack::analyze()
{
if (m_stackState != ParsedAndImported || m_stackState >= AnalysisPerformed)
Expand Down Expand Up @@ -600,6 +622,9 @@ bool CompilerStack::parseAndAnalyze(State _stopAfter)
{
m_stopAfter = _stopAfter;

if (!m_evmAssemblyJson.empty())
return true;

bool success = parse();
if (m_stackState >= m_stopAfter)
return success;
Expand Down Expand Up @@ -649,55 +674,85 @@ bool CompilerStack::compile(State _stopAfter)
// Only compile contracts individually which have been requested.
map<ContractDefinition const*, shared_ptr<Compiler const>> otherCompilers;

for (Source const* source: m_sourceOrder)
for (ASTPointer<ASTNode> const& node: source->ast->nodes())
if (auto contract = dynamic_cast<ContractDefinition const*>(node.get()))
if (isRequestedContract(*contract))
{
try
if (!m_evmAssemblyJson.empty())
{
solAssert(m_importedSources, "");
solAssert(m_evmAssemblyJson.size() == 1, "");

string const evmSourceName = m_evmAssemblyJson.begin()->first;
Json::Value const evmJson = m_evmAssemblyJson.begin()->second;

evmasm::Assembly::OptimiserSettings optimiserSettings;
Copy link
Member Author

@aarlt aarlt Mar 14, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I saw somewhere a optimiser-settings transform function.

optimiserSettings.evmVersion = m_evmVersion;
optimiserSettings.expectedExecutionsPerDeployment = m_optimiserSettings.expectedExecutionsPerDeployment;
optimiserSettings.runCSE = m_optimiserSettings.runCSE;
optimiserSettings.runConstantOptimiser = m_optimiserSettings.runConstantOptimiser;
optimiserSettings.runDeduplicate = m_optimiserSettings.runDeduplicate;
optimiserSettings.runInliner = m_optimiserSettings.runInliner;
optimiserSettings.runJumpdestRemover = m_optimiserSettings.runJumpdestRemover;
optimiserSettings.runPeephole = m_optimiserSettings.runPeephole;

m_contracts[evmSourceName].evmAssembly = make_shared<evmasm::Assembly>(true, evmSourceName);
m_contracts[evmSourceName].evmAssembly->loadFromAssemblyJSON(m_evmAssemblyJson[evmSourceName]);
if (m_optimiserSettings.enabled)
m_contracts[evmSourceName].evmAssembly->optimise(optimiserSettings);
Copy link
Member Author

@aarlt aarlt Mar 14, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@chriseth @ekpyron I do the optimisation here like this. Does it make sense?

m_contracts[evmSourceName].object = m_contracts[evmSourceName].evmAssembly->assemble();

m_contracts[evmSourceName].evmRuntimeAssembly = make_shared<evmasm::Assembly>(false, evmSourceName);
m_contracts[evmSourceName].evmRuntimeAssembly->setSources(m_contracts[evmSourceName].evmAssembly->sources());
m_contracts[evmSourceName].evmRuntimeAssembly->loadFromAssemblyJSON(m_evmAssemblyJson[evmSourceName][".data"]["0"], false);
Copy link
Member Author

@aarlt aarlt Mar 14, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@chriseth @ekpyron Does it make sense to load the runtime assembly hardcoded from ".data"[0] here? Not sure whether we should do this dynamically - e.g. that the correct run time will be taken automatically. I guess for this I would need to analyse the "deploy" code. Not sure whether we really want this. What are your opinions?

if (m_optimiserSettings.enabled)
m_contracts[evmSourceName].evmRuntimeAssembly->optimise(optimiserSettings);
Copy link
Member Author

@aarlt aarlt Mar 14, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@chriseth @ekpyron Similar optimisation as above. But for the runtime part. Does it make sense?

m_contracts[evmSourceName].runtimeObject = m_contracts[evmSourceName].evmRuntimeAssembly->assemble();
}
else
{
for (Source const* source: m_sourceOrder)
for (ASTPointer<ASTNode> const& node: source->ast->nodes())
if (auto contract = dynamic_cast<ContractDefinition const*>(node.get()))
if (isRequestedContract(*contract))
{
if (m_viaIR || m_generateIR || m_generateEwasm)
generateIR(*contract);
if (m_generateEvmBytecode)
try
{
if (m_viaIR)
generateEVMFromIR(*contract);
else
compileContract(*contract, otherCompilers);
if (m_viaIR || m_generateIR || m_generateEwasm)
generateIR(*contract);
if (m_generateEvmBytecode)
{
if (m_viaIR)
generateEVMFromIR(*contract);
else
compileContract(*contract, otherCompilers);
}
if (m_generateEwasm)
generateEwasm(*contract);
}
if (m_generateEwasm)
generateEwasm(*contract);
}
catch (Error const& _error)
{
if (_error.type() != Error::Type::CodeGenerationError)
throw;
m_errorReporter.error(_error.errorId(), _error.type(), SourceLocation(), _error.what());
return false;
}
catch (UnimplementedFeatureError const& _unimplementedError)
{
if (
SourceLocation const* sourceLocation =
boost::get_error_info<langutil::errinfo_sourceLocation>(_unimplementedError)
)
catch (Error const& _error)
{
string const* comment = _unimplementedError.comment();
m_errorReporter.error(
1834_error,
Error::Type::CodeGenerationError,
*sourceLocation,
"Unimplemented feature error" +
((comment && !comment->empty()) ? ": " + *comment : string{}) +
" in " +
_unimplementedError.lineInfo()
);
if (_error.type() != Error::Type::CodeGenerationError)
throw;
m_errorReporter.error(_error.errorId(), _error.type(), SourceLocation(), _error.what());
return false;
}
else
throw;
catch (UnimplementedFeatureError const& _unimplementedError)
{
if (SourceLocation const* sourceLocation
= boost::get_error_info<langutil::errinfo_sourceLocation>(_unimplementedError))
{
string const* comment = _unimplementedError.comment();
m_errorReporter.error(
1834_error,
Error::Type::CodeGenerationError,
*sourceLocation,
"Unimplemented feature error"
+ ((comment && !comment->empty()) ? ": " + *comment : string{}) + " in "
+ _unimplementedError.lineInfo());
return false;
}
else
throw;
}
}
}
}
m_stackState = CompilationSuccessful;
this->link();
return true;
Expand Down Expand Up @@ -933,14 +988,21 @@ vector<string> CompilerStack::sourceNames() const
return names;
}

map<string, unsigned> CompilerStack::sourceIndices() const
map<string, unsigned> CompilerStack::sourceIndices(bool _includeInternalSources /* = true */) const
{
map<string, unsigned> indices;
unsigned index = 0;
for (auto const& s: m_sources)
indices[s.first] = index++;
solAssert(!indices.count(CompilerContext::yulUtilityFileName()), "");
indices[CompilerContext::yulUtilityFileName()] = index++;
if (m_evmAssemblyJson.empty())
{
for (auto const& s: m_sources)
indices[s.first] = index++;
solAssert(!indices.count(CompilerContext::yulUtilityFileName()), "");
if (_includeInternalSources)
indices[CompilerContext::yulUtilityFileName()] = index++;
}
else
for (auto const& s: m_sourceOrder)
indices[s->charStream->source()] = index++;
return indices;
}

Expand Down
7 changes: 6 additions & 1 deletion libsolidity/interface/CompilerStack.h
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,10 @@ class CompilerStack: public langutil::CharStreamProvider
/// Will throw errors if the import fails
void importASTs(std::map<std::string, Json::Value> const& _sources);

/// Imports given Evm Assembly Json. Leads to the same internal state as parse().
/// Will throw errors if the import fails
void importEvmAssemblyJson(std::map<std::string, Json::Value> const& _sources);

/// Performs the analysis steps (imports, scopesetting, syntaxCheck, referenceResolving,
/// typechecking, staticAnalysis) on previously parsed sources.
/// @returns false on error.
Expand All @@ -240,7 +244,7 @@ class CompilerStack: public langutil::CharStreamProvider

/// @returns a mapping assigning each source name its index inside the vector returned
/// by sourceNames().
std::map<std::string, unsigned> sourceIndices() const;
std::map<std::string, unsigned> sourceIndices(bool _includeInternalSources = true) const;

/// @returns the previously used character stream, useful for counting lines during error reporting.
langutil::CharStream const& charStream(std::string const& _sourceName) const override;
Expand Down Expand Up @@ -499,6 +503,7 @@ class CompilerStack: public langutil::CharStreamProvider
std::map<std::string const, Source> m_sources;
// if imported, store AST-JSONS for each filename
std::map<std::string, Json::Value> m_sourceJsons;
std::map<std::string, Json::Value> m_evmAssemblyJson;
std::vector<std::string> m_unhandledSMTLib2Queries;
std::map<util::h256, std::string> m_smtlib2Responses;
std::shared_ptr<GlobalContext> m_globalContext;
Expand Down
2 changes: 2 additions & 0 deletions libsolidity/interface/OptimiserSettings.h
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,8 @@ struct OptimiserSettings
/// This specifies an estimate on how often each opcode in this assembly will be executed,
/// i.e. use a small value to optimise for size and a large value to optimise for runtime gas usage.
size_t expectedExecutionsPerDeployment = 200;
/// Flag reflecting whether optimizer is enabled.
bool enabled = false;
};

}
Loading