Skip to content
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
Collaborator 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
Collaborator 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
Collaborator 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
Collaborator 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