Merge remote-tracking branch 'origin/develop' into breaking

This commit is contained in:
chriseth
2022-03-16 15:41:37 +01:00
542 changed files with 6696 additions and 2399 deletions
+9
View File
@@ -144,4 +144,13 @@ template <class... Args> inline std::shared_ptr<DebugData const> debugDataOf(std
return std::visit([](auto const& _arg) { return debugDataOf(_arg); }, _node);
}
inline bool hasDefaultCase(Switch const& _switch)
{
return std::any_of(
_switch.cases.begin(),
_switch.cases.end(),
[](Case const& _case) { return !_case.value; }
);
}
}
+7 -4
View File
@@ -38,6 +38,7 @@
#include <libevmasm/Assembly.h>
#include <liblangutil/Scanner.h>
#include <boost/algorithm/string.hpp>
#include <optional>
using namespace std;
@@ -71,8 +72,7 @@ evmasm::Assembly::OptimiserSettings translateOptimiserSettings(
)
{
// Constructing it this way so that we notice changes in the fields.
evmasm::Assembly::OptimiserSettings asmSettings{false, false, false, false, false, false, false, _evmVersion, 0};
asmSettings.isCreation = true;
evmasm::Assembly::OptimiserSettings asmSettings{false, false, false, false, false, false, _evmVersion, 0};
asmSettings.runInliner = _settings.runInliner;
asmSettings.runJumpdestRemover = _settings.runJumpdestRemover;
asmSettings.runPeephole = _settings.runPeephole;
@@ -194,7 +194,10 @@ void AssemblyStack::optimize(Object& _object, bool _isCreation)
yulAssert(_object.analysisInfo, "");
for (auto& subNode: _object.subObjects)
if (auto subObject = dynamic_cast<Object*>(subNode.get()))
optimize(*subObject, false);
{
bool isCreation = !boost::ends_with(subObject->name.str(), "_deployed");
optimize(*subObject, isCreation);
}
Dialect const& dialect = languageToDialect(m_language, m_evmVersion);
unique_ptr<GasMeter> meter;
@@ -281,7 +284,7 @@ AssemblyStack::assembleEVMWithDeployed(optional<string_view> _deployName) const
yulAssert(m_parserResult->code, "");
yulAssert(m_parserResult->analysisInfo, "");
evmasm::Assembly assembly;
evmasm::Assembly assembly(true, {});
EthAssemblyAdapter adapter(assembly);
compileEVM(adapter, m_optimiserSettings.optimizeStackAllocation);
+2
View File
@@ -179,6 +179,8 @@ add_library(yul
optimiser/UnusedAssignEliminator.h
optimiser/UnusedStoreBase.cpp
optimiser/UnusedStoreBase.h
optimiser/UnusedStoreEliminator.cpp
optimiser/UnusedStoreEliminator.h
optimiser/Rematerialiser.cpp
optimiser/Rematerialiser.h
optimiser/SMTSolver.cpp
+2 -2
View File
@@ -91,13 +91,13 @@ string Object::toString(
set<YulString> Object::qualifiedDataNames() const
{
set<YulString> qualifiedNames =
name.empty() || contains(name.str(), '.') ?
name.empty() || util::contains(name.str(), '.') ?
set<YulString>{} :
set<YulString>{name};
for (shared_ptr<ObjectNode> const& subObjectNode: subObjects)
{
yulAssert(qualifiedNames.count(subObjectNode->name) == 0, "");
if (contains(subObjectNode->name.str(), '.'))
if (util::contains(subObjectNode->name.str(), '.'))
continue;
qualifiedNames.insert(subObjectNode->name);
if (auto const* subObject = dynamic_cast<Object const*>(subObjectNode.get()))
+1 -1
View File
@@ -98,7 +98,7 @@ public:
/// Append the assembled size as a constant.
virtual void appendAssemblySize() = 0;
/// Creates a new sub-assembly, which can be referenced using dataSize and dataOffset.
virtual std::pair<std::shared_ptr<AbstractAssembly>, SubID> createSubAssembly(std::string _name = "") = 0;
virtual std::pair<std::shared_ptr<AbstractAssembly>, SubID> createSubAssembly(bool _creation, std::string _name = "") = 0;
/// Appends the offset of the given sub-assembly or data.
virtual void appendDataOffset(std::vector<SubID> const& _subPath) = 0;
/// Appends the size of the given sub-assembly or data.
+9
View File
@@ -195,6 +195,14 @@ struct CFG
std::shared_ptr<DebugData const> debugData;
std::vector<BasicBlock*> entries;
std::vector<Operation> operations;
/// True, if the block is the beginning of a disconnected subgraph. That is, if no block that is reachable
/// from this block is an ancestor of this block. In other words, this is true, if this block is the target
/// of a cut-edge/bridge in the CFG or if the block itself terminates.
bool isStartOfSubGraph = false;
/// True, if there is a path from this block to a function return.
bool needsCleanStack = false;
/// If the block starts a sub-graph and does not lead to a function return, we are free to add junk to it.
bool allowsJunk() const { return isStartOfSubGraph && !needsCleanStack; }
std::variant<MainExit, Jump, ConditionalJump, FunctionReturn, Terminated> exit = MainExit{};
};
@@ -205,6 +213,7 @@ struct CFG
BasicBlock* entry = nullptr;
std::vector<VariableSlot> parameters;
std::vector<VariableSlot> returnVariables;
std::vector<BasicBlock*> exits;
};
/// The main entry point, i.e. the start of the outermost Yul block.
@@ -48,7 +48,7 @@ using namespace std;
namespace
{
// Removes edges to blocks that are not reachable.
/// Removes edges to blocks that are not reachable.
void cleanUnreachable(CFG& _cfg)
{
// Determine which blocks are reachable from the entry.
@@ -77,7 +77,8 @@ void cleanUnreachable(CFG& _cfg)
return !reachabilityCheck.visited.count(entry);
});
}
// Sets the ``recursive`` member to ``true`` for all recursive function calls.
/// Sets the ``recursive`` member to ``true`` for all recursive function calls.
void markRecursiveCalls(CFG& _cfg)
{
map<CFG::BasicBlock*, vector<CFG::FunctionCall*>> callsPerBlock;
@@ -124,6 +125,84 @@ void markRecursiveCalls(CFG& _cfg)
});
}
}
/// Marks each cut-vertex in the CFG, i.e. each block that begins a disconnected sub-graph of the CFG.
/// Entering such a block means that control flow will never return to a previously visited block.
void markStartsOfSubGraphs(CFG& _cfg)
{
vector<CFG::BasicBlock*> entries;
entries.emplace_back(_cfg.entry);
for (auto&& functionInfo: _cfg.functionInfo | ranges::views::values)
entries.emplace_back(functionInfo.entry);
for (auto& entry: entries)
{
/**
* Detect bridges following Algorithm 1 in https://arxiv.org/pdf/2108.07346.pdf
* and mark the bridge targets as starts of sub-graphs.
*/
set<CFG::BasicBlock*> visited;
map<CFG::BasicBlock*, size_t> disc;
map<CFG::BasicBlock*, size_t> low;
map<CFG::BasicBlock*, CFG::BasicBlock*> parent;
size_t time = 0;
auto dfs = [&](CFG::BasicBlock* _u, auto _recurse) -> void {
visited.insert(_u);
disc[_u] = low[_u] = time;
time++;
vector<CFG::BasicBlock*> children = _u->entries;
visit(util::GenericVisitor{
[&](CFG::BasicBlock::Jump const& _jump) {
children.emplace_back(_jump.target);
},
[&](CFG::BasicBlock::ConditionalJump const& _jump) {
children.emplace_back(_jump.zero);
children.emplace_back(_jump.nonZero);
},
[&](CFG::BasicBlock::FunctionReturn const&) {},
[&](CFG::BasicBlock::Terminated const&) { _u->isStartOfSubGraph = true; },
[&](CFG::BasicBlock::MainExit const&) { _u->isStartOfSubGraph = true; }
}, _u->exit);
yulAssert(!util::contains(children, _u));
for (CFG::BasicBlock* v: children)
if (!visited.count(v))
{
parent[v] = _u;
_recurse(v, _recurse);
low[_u] = min(low[_u], low[v]);
if (low[v] > disc[_u])
{
// _u <-> v is a cut edge in the undirected graph
bool edgeVtoU = util::contains(_u->entries, v);
bool edgeUtoV = util::contains(v->entries, _u);
if (edgeVtoU && !edgeUtoV)
// Cut edge v -> _u
_u->isStartOfSubGraph = true;
else if (edgeUtoV && !edgeVtoU)
// Cut edge _u -> v
v->isStartOfSubGraph = true;
}
}
else if (v != parent[_u])
low[_u] = min(low[_u], disc[v]);
};
dfs(entry, dfs);
}
}
/// Marks each block that needs to maintain a clean stack. That is each block that has an outgoing
/// path to a function return.
void markNeedsCleanStack(CFG& _cfg)
{
for (auto& functionInfo: _cfg.functionInfo | ranges::views::values)
for (CFG::BasicBlock* exit: functionInfo.exits)
util::BreadthFirstSearch<CFG::BasicBlock*>{{exit}}.run([&](CFG::BasicBlock* _block, auto _addChild) {
_block->needsCleanStack = true;
for (CFG::BasicBlock* entry: _block->entries)
_addChild(entry);
});
}
}
std::unique_ptr<CFG> ControlFlowGraphBuilder::build(
@@ -141,6 +220,8 @@ std::unique_ptr<CFG> ControlFlowGraphBuilder::build(
cleanUnreachable(*result);
markRecursiveCalls(*result);
markStartsOfSubGraphs(*result);
markNeedsCleanStack(*result);
// TODO: It might be worthwhile to run some further simplifications on the graph itself here.
// E.g. if there is a jump to a node that has the jumping node as its only entry, the nodes can be fused, etc.
@@ -379,6 +460,7 @@ void ControlFlowGraphBuilder::operator()(Leave const& leave_)
{
yulAssert(m_currentFunction.has_value(), "");
m_currentBlock->exit = CFG::BasicBlock::FunctionReturn{debugDataOf(leave_), *m_currentFunction};
(*m_currentFunction)->exits.emplace_back(m_currentBlock);
m_currentBlock = &m_graph.makeBlock(debugDataOf(*m_currentBlock));
}
@@ -395,6 +477,7 @@ void ControlFlowGraphBuilder::operator()(FunctionDefinition const& _function)
builder.m_currentFunction = &functionInfo;
builder.m_currentBlock = functionInfo.entry;
builder(_function.body);
functionInfo.exits.emplace_back(builder.m_currentBlock);
builder.m_currentBlock->exit = CFG::BasicBlock::FunctionReturn{debugDataOf(_function), &functionInfo};
}
@@ -423,7 +506,8 @@ void ControlFlowGraphBuilder::registerFunction(FunctionDefinition const& _functi
std::get<Scope::Variable>(virtualFunctionScope->identifiers.at(_retVar.name)),
_retVar.debugData
};
}) | ranges::to<vector>
}) | ranges::to<vector>,
{}
})).second;
yulAssert(inserted);
}
+9 -11
View File
@@ -21,17 +21,17 @@
#include <libyul/backends/evm/EVMDialect.h>
#include <libyul/AsmAnalysisInfo.h>
#include <libevmasm/Instruction.h>
#include <libevmasm/SemanticInformation.h>
#include <liblangutil/Exceptions.h>
#include <libsolutil/StringUtils.h>
#include <libyul/AST.h>
#include <libyul/Object.h>
#include <libyul/Exceptions.h>
#include <libyul/AsmAnalysisInfo.h>
#include <libyul/AsmParser.h>
#include <libyul/Exceptions.h>
#include <libyul/Object.h>
#include <libyul/Utilities.h>
#include <libyul/backends/evm/AbstractAssembly.h>
#include <libevmasm/SemanticInformation.h>
#include <libevmasm/Instruction.h>
#include <liblangutil/Exceptions.h>
#include <range/v3/view/reverse.hpp>
#include <range/v3/view/tail.hpp>
@@ -114,8 +114,7 @@ set<YulString> createReservedIdentifiers()
set<YulString> reserved;
for (auto const& instr: evmasm::c_instructions)
{
string name = instr.first;
transform(name.begin(), name.end(), name.begin(), [](unsigned char _c) { return tolower(_c); });
string name = toLower(instr.first);
reserved.emplace(name);
}
reserved += vector<YulString>{
@@ -134,8 +133,7 @@ map<YulString, BuiltinFunctionForEVM> createBuiltins(langutil::EVMVersion _evmVe
map<YulString, BuiltinFunctionForEVM> builtins;
for (auto const& instr: evmasm::c_instructions)
{
string name = instr.first;
transform(name.begin(), name.end(), name.begin(), [](unsigned char _c) { return tolower(_c); });
string name = toLower(instr.first);
auto const opcode = instr.second;
if (
+5 -2
View File
@@ -30,6 +30,8 @@
#include <libyul/Object.h>
#include <libyul/Exceptions.h>
#include <boost/algorithm/string.hpp>
using namespace solidity::yul;
using namespace std;
@@ -48,7 +50,8 @@ void EVMObjectCompiler::run(Object& _object, bool _optimize)
for (auto const& subNode: _object.subObjects)
if (auto* subObject = dynamic_cast<Object*>(subNode.get()))
{
auto subAssemblyAndID = m_assembly.createSubAssembly(subObject->name.str());
bool isCreation = !boost::ends_with(subObject->name.str(), "_deployed");
auto subAssemblyAndID = m_assembly.createSubAssembly(isCreation, subObject->name.str());
context.subIDs[subObject->name] = subAssemblyAndID.second;
subObject->subId = subAssemblyAndID.second;
compile(*subObject, *subAssemblyAndID.first, m_dialect, _optimize);
@@ -86,7 +89,7 @@ void EVMObjectCompiler::run(Object& _object, bool _optimize)
if (memoryGuardCalls.empty())
msg += "\nNo memoryguard was present. "
"Consider using memory-safe assembly only and annotating it via "
"\"/// @solidity memory-safe-assembly\".";
"'assembly (\"memory-safe\") { ... }'.";
else
msg += "\nmemoryguard was present.";
stackError << util::errinfo_comment(msg);
+2 -2
View File
@@ -122,9 +122,9 @@ void EthAssemblyAdapter::appendAssemblySize()
m_assembly.appendProgramSize();
}
pair<shared_ptr<AbstractAssembly>, AbstractAssembly::SubID> EthAssemblyAdapter::createSubAssembly(string _name)
pair<shared_ptr<AbstractAssembly>, AbstractAssembly::SubID> EthAssemblyAdapter::createSubAssembly(bool _creation, string _name)
{
shared_ptr<evmasm::Assembly> assembly{make_shared<evmasm::Assembly>(std::move(_name))};
shared_ptr<evmasm::Assembly> assembly{make_shared<evmasm::Assembly>(_creation, std::move(_name))};
auto sub = m_assembly.newSub(assembly);
return {make_shared<EthAssemblyAdapter>(*assembly), static_cast<size_t>(sub.data())};
}
+1 -1
View File
@@ -55,7 +55,7 @@ public:
void appendJumpTo(LabelID _labelId, int _stackDiffAfter, JumpType _jumpType) override;
void appendJumpToIf(LabelID _labelId, JumpType _jumpType) override;
void appendAssemblySize() override;
std::pair<std::shared_ptr<AbstractAssembly>, SubID> createSubAssembly(std::string _name = {}) override;
std::pair<std::shared_ptr<AbstractAssembly>, SubID> createSubAssembly(bool _creation, std::string _name = {}) override;
void appendDataOffset(std::vector<SubID> const& _subPath) override;
void appendDataSize(std::vector<SubID> const& _subPath) override;
SubID appendData(bytes const& _data) override;
+1 -1
View File
@@ -98,7 +98,7 @@ void NoOutputAssembly::appendAssemblySize()
appendInstruction(evmasm::Instruction::PUSH1);
}
pair<shared_ptr<AbstractAssembly>, AbstractAssembly::SubID> NoOutputAssembly::createSubAssembly(std::string)
pair<shared_ptr<AbstractAssembly>, AbstractAssembly::SubID> NoOutputAssembly::createSubAssembly(bool, std::string)
{
yulAssert(false, "Sub assemblies not implemented.");
return {};
+1 -1
View File
@@ -65,7 +65,7 @@ public:
void appendJumpToIf(LabelID _labelId, JumpType _jumpType) override;
void appendAssemblySize() override;
std::pair<std::shared_ptr<AbstractAssembly>, SubID> createSubAssembly(std::string _name = "") override;
std::pair<std::shared_ptr<AbstractAssembly>, SubID> createSubAssembly(bool _creation, std::string _name = "") override;
void appendDataOffset(std::vector<SubID> const& _subPath) override;
void appendDataSize(std::vector<SubID> const& _subPath) override;
SubID appendData(bytes const& _data) override;
@@ -23,6 +23,8 @@
#include <libyul/backends/evm/StackHelpers.h>
#include <libevmasm/GasMeter.h>
#include <libsolutil/Algorithms.h>
#include <libsolutil/cxx20.h>
#include <libsolutil/Visitor.h>
@@ -400,6 +402,7 @@ void StackLayoutGenerator::processEntryPoint(CFG::BasicBlock const& _entry)
}
stitchConditionalJumps(_entry);
fillInJunk(_entry);
}
optional<Stack> StackLayoutGenerator::getExitLayoutOrStageDependencies(
@@ -703,3 +706,110 @@ Stack StackLayoutGenerator::compressStack(Stack _stack)
while (firstDupOffset);
return _stack;
}
void StackLayoutGenerator::fillInJunk(CFG::BasicBlock const& _block)
{
/// Recursively adds junk to the subgraph starting on @a _entry.
/// Since it is only called on cut-vertices, the full subgraph retains proper stack balance.
auto addJunkRecursive = [&](CFG::BasicBlock const* _entry, size_t _numJunk) {
util::BreadthFirstSearch<CFG::BasicBlock const*> breadthFirstSearch{{_entry}};
breadthFirstSearch.run([&](CFG::BasicBlock const* _block, auto _addChild) {
auto& blockInfo = m_layout.blockInfos.at(_block);
blockInfo.entryLayout = Stack{_numJunk, JunkSlot{}} + move(blockInfo.entryLayout);
for (auto const& operation: _block->operations)
{
auto& operationEntryLayout = m_layout.operationEntryLayout.at(&operation);
operationEntryLayout = Stack{_numJunk, JunkSlot{}} + move(operationEntryLayout);
}
blockInfo.exitLayout = Stack{_numJunk, JunkSlot{}} + move(blockInfo.exitLayout);
std::visit(util::GenericVisitor{
[&](CFG::BasicBlock::MainExit const&) {},
[&](CFG::BasicBlock::Jump const& _jump)
{
_addChild(_jump.target);
},
[&](CFG::BasicBlock::ConditionalJump const& _conditionalJump)
{
_addChild(_conditionalJump.zero);
_addChild(_conditionalJump.nonZero);
},
[&](CFG::BasicBlock::FunctionReturn const&) { yulAssert(false); },
[&](CFG::BasicBlock::Terminated const&) {},
}, _block->exit);
});
};
/// @returns the number of operations required to transform @a _source to @a _target.
auto evaluateTransform = [](Stack _source, Stack const& _target) -> size_t {
size_t opGas = 0;
auto swap = [&](unsigned _swapDepth)
{
if (_swapDepth > 16)
opGas += 1000;
else
opGas += evmasm::GasMeter::runGas(evmasm::swapInstruction(_swapDepth));
};
auto dupOrPush = [&](StackSlot const& _slot)
{
if (canBeFreelyGenerated(_slot))
opGas += evmasm::GasMeter::runGas(evmasm::pushInstruction(32));
else
{
auto depth = util::findOffset(_source | ranges::views::reverse, _slot);
yulAssert(depth);
if (*depth < 16)
opGas += evmasm::GasMeter::runGas(evmasm::dupInstruction(static_cast<unsigned>(*depth + 1)));
else
opGas += 1000;
}
};
auto pop = [&]() { opGas += evmasm::GasMeter::runGas(evmasm::Instruction::POP); };
createStackLayout(_source, _target, swap, dupOrPush, pop);
return opGas;
};
/// Traverses the CFG and at each block that allows junk, i.e. that is a cut-vertex that never leads to a function
/// return, checks if adding junk reduces the shuffling cost upon entering and if so recursively adds junk
/// to the spanned subgraph.
util::BreadthFirstSearch<CFG::BasicBlock const*>{{&_block}}.run([&](CFG::BasicBlock const* _block, auto _addChild) {
std::visit(util::GenericVisitor{
[&](CFG::BasicBlock::MainExit const&) {},
[&](CFG::BasicBlock::Jump const& _jump)
{
_addChild(_jump.target);
},
[&](CFG::BasicBlock::ConditionalJump const& _conditionalJump)
{
for (CFG::BasicBlock* exit: {_conditionalJump.zero, _conditionalJump.nonZero})
if (exit->allowsJunk())
{
auto& blockInfo = m_layout.blockInfos.at(exit);
Stack entryLayout = blockInfo.entryLayout;
Stack nextLayout = exit->operations.empty() ? blockInfo.exitLayout : m_layout.operationEntryLayout.at(&exit->operations.front());
size_t bestCost = evaluateTransform(entryLayout, nextLayout);
size_t bestNumJunk = 0;
size_t maxJunk = entryLayout.size();
for (size_t numJunk = 1; numJunk <= maxJunk; ++numJunk)
{
size_t cost = evaluateTransform(entryLayout, Stack{numJunk, JunkSlot{}} + nextLayout);
if (cost < bestCost)
{
bestCost = cost;
bestNumJunk = numJunk;
}
}
if (bestNumJunk > 0)
{
addJunkRecursive(exit, bestNumJunk);
blockInfo.entryLayout = entryLayout;
}
}
_addChild(_conditionalJump.zero);
_addChild(_conditionalJump.nonZero);
},
[&](CFG::BasicBlock::FunctionReturn const&) {},
[&](CFG::BasicBlock::Terminated const&) {},
}, _block->exit);
});
}
@@ -111,6 +111,9 @@ private:
/// stack @a _stack.
static Stack compressStack(Stack _stack);
//// Fills in junk when entering branches that do not need a clean stack in case the result is cheaper.
void fillInJunk(CFG::BasicBlock const& _block);
StackLayout& m_layout;
};
@@ -95,10 +95,10 @@ void CommonSubexpressionEliminator::visit(Expression& _e)
if (Identifier const* identifier = get_if<Identifier>(&_e))
{
YulString identifierName = identifier->name;
if (m_value.count(identifierName))
if (AssignedValue const* assignedValue = variableValue(identifierName))
{
assertThrow(m_value.at(identifierName).value, OptimizerException, "");
if (Identifier const* value = get_if<Identifier>(m_value.at(identifierName).value))
assertThrow(assignedValue->value, OptimizerException, "");
if (Identifier const* value = get_if<Identifier>(assignedValue->value))
if (inScope(value->name))
_e = Identifier{debugDataOf(_e), value->name};
}
@@ -106,7 +106,7 @@ void CommonSubexpressionEliminator::visit(Expression& _e)
else
{
// TODO this search is rather inefficient.
for (auto const& [variable, value]: m_value)
for (auto const& [variable, value]: allValues())
{
assertThrow(value.value, OptimizerException, "");
// Prevent using the default value of return variables
+1 -7
View File
@@ -60,13 +60,7 @@ void removeEmptyDefaultFromSwitch(Switch& _switchStmt)
void removeEmptyCasesFromSwitch(Switch& _switchStmt)
{
bool hasDefault = std::any_of(
_switchStmt.cases.begin(),
_switchStmt.cases.end(),
[](Case const& _case) { return !_case.value; }
);
if (hasDefault)
if (hasDefaultCase(_switchStmt))
return;
ranges::actions::remove_if(
+53 -39
View File
@@ -24,6 +24,7 @@
#include <libyul/optimiser/NameCollector.h>
#include <libyul/optimiser/Semantics.h>
#include <libyul/optimiser/KnowledgeBase.h>
#include <libyul/AST.h>
#include <libyul/Dialect.h>
#include <libyul/Exceptions.h>
@@ -47,7 +48,7 @@ DataFlowAnalyzer::DataFlowAnalyzer(
):
m_dialect(_dialect),
m_functionSideEffects(std::move(_functionSideEffects)),
m_knowledgeBase(_dialect, m_value)
m_knowledgeBase(_dialect, [this](YulString _var) { return variableValue(_var); })
{
if (auto const* builtin = _dialect.memoryStoreFunction(YulString{}))
m_storeFunctionName[static_cast<unsigned>(StoreLoadLocation::Memory)] = builtin->name;
@@ -64,20 +65,20 @@ void DataFlowAnalyzer::operator()(ExpressionStatement& _statement)
if (auto vars = isSimpleStore(StoreLoadLocation::Storage, _statement))
{
ASTModifier::operator()(_statement);
cxx20::erase_if(m_storage, mapTuple([&](auto&& key, auto&& value) {
cxx20::erase_if(m_state.storage, mapTuple([&](auto&& key, auto&& value) {
return
!m_knowledgeBase.knownToBeDifferent(vars->first, key) &&
!m_knowledgeBase.knownToBeEqual(vars->second, value);
}));
m_storage[vars->first] = vars->second;
m_state.storage[vars->first] = vars->second;
}
else if (auto vars = isSimpleStore(StoreLoadLocation::Memory, _statement))
{
ASTModifier::operator()(_statement);
cxx20::erase_if(m_memory, mapTuple([&](auto&& key, auto&& /* value */) {
cxx20::erase_if(m_state.memory, mapTuple([&](auto&& key, auto&& /* value */) {
return !m_knowledgeBase.knownToBeDifferentByAtLeast32(vars->first, key);
}));
m_memory[vars->first] = vars->second;
m_state.memory[vars->first] = vars->second;
}
else
{
@@ -116,8 +117,8 @@ void DataFlowAnalyzer::operator()(VariableDeclaration& _varDecl)
void DataFlowAnalyzer::operator()(If& _if)
{
clearKnowledgeIfInvalidated(*_if.condition);
unordered_map<YulString, YulString> storage = m_storage;
unordered_map<YulString, YulString> memory = m_memory;
unordered_map<YulString, YulString> storage = m_state.storage;
unordered_map<YulString, YulString> memory = m_state.memory;
ASTModifier::operator()(_if);
@@ -133,8 +134,8 @@ void DataFlowAnalyzer::operator()(Switch& _switch)
set<YulString> assignedVariables;
for (auto& _case: _switch.cases)
{
unordered_map<YulString, YulString> storage = m_storage;
unordered_map<YulString, YulString> memory = m_memory;
unordered_map<YulString, YulString> storage = m_state.storage;
unordered_map<YulString, YulString> memory = m_state.memory;
(*this)(_case.body);
joinKnowledge(storage, memory);
@@ -153,11 +154,8 @@ void DataFlowAnalyzer::operator()(FunctionDefinition& _fun)
{
// Save all information. We might rather reinstantiate this class,
// but this could be difficult if it is subclassed.
ScopedSaveAndRestore valueResetter(m_value, {});
ScopedSaveAndRestore stateResetter(m_state, {});
ScopedSaveAndRestore loopDepthResetter(m_loopDepth, 0u);
ScopedSaveAndRestore referencesResetter(m_references, {});
ScopedSaveAndRestore storageResetter(m_storage, {});
ScopedSaveAndRestore memoryResetter(m_memory, {});
pushScope(true);
for (auto const& parameter: _fun.parameters)
@@ -218,6 +216,22 @@ void DataFlowAnalyzer::operator()(Block& _block)
assertThrow(numScopes == m_variableScopes.size(), OptimizerException, "");
}
optional<YulString> DataFlowAnalyzer::storageValue(YulString _key) const
{
if (YulString const* value = util::valueOrNullptr(m_state.storage, _key))
return *value;
else
return nullopt;
}
optional<YulString> DataFlowAnalyzer::memoryValue(YulString _key) const
{
if (YulString const* value = util::valueOrNullptr(m_state.memory, _key))
return *value;
else
return nullopt;
}
void DataFlowAnalyzer::handleAssignment(set<YulString> const& _variables, Expression* _value, bool _isDeclaration)
{
if (!_isDeclaration)
@@ -242,17 +256,17 @@ void DataFlowAnalyzer::handleAssignment(set<YulString> const& _variables, Expres
auto const& referencedVariables = movableChecker.referencedVariables();
for (auto const& name: _variables)
{
m_references[name] = referencedVariables;
m_state.references[name] = referencedVariables;
if (!_isDeclaration)
{
// assignment to slot denoted by "name"
m_storage.erase(name);
m_state.storage.erase(name);
// assignment to slot contents denoted by "name"
cxx20::erase_if(m_storage, mapTuple([&name](auto&& /* key */, auto&& value) { return value == name; }));
cxx20::erase_if(m_state.storage, mapTuple([&name](auto&& /* key */, auto&& value) { return value == name; }));
// assignment to slot denoted by "name"
m_memory.erase(name);
m_state.memory.erase(name);
// assignment to slot contents denoted by "name"
cxx20::erase_if(m_memory, mapTuple([&name](auto&& /* key */, auto&& value) { return value == name; }));
cxx20::erase_if(m_state.memory, mapTuple([&name](auto&& /* key */, auto&& value) { return value == name; }));
}
}
@@ -265,9 +279,9 @@ void DataFlowAnalyzer::handleAssignment(set<YulString> const& _variables, Expres
// On the other hand, if we knew the value in the slot
// already, then the sload() / mload() would have been replaced by a variable anyway.
if (auto key = isSimpleLoad(StoreLoadLocation::Memory, *_value))
m_memory[*key] = variable;
m_state.memory[*key] = variable;
else if (auto key = isSimpleLoad(StoreLoadLocation::Storage, *_value))
m_storage[*key] = variable;
m_state.storage[*key] = variable;
}
}
}
@@ -281,8 +295,8 @@ void DataFlowAnalyzer::popScope()
{
for (auto const& name: m_variableScopes.back().variables)
{
m_value.erase(name);
m_references.erase(name);
m_state.value.erase(name);
m_state.references.erase(name);
}
m_variableScopes.pop_back();
}
@@ -308,44 +322,44 @@ void DataFlowAnalyzer::clearValues(set<YulString> _variables)
auto eraseCondition = mapTuple([&_variables](auto&& key, auto&& value) {
return _variables.count(key) || _variables.count(value);
});
cxx20::erase_if(m_storage, eraseCondition);
cxx20::erase_if(m_memory, eraseCondition);
cxx20::erase_if(m_state.storage, eraseCondition);
cxx20::erase_if(m_state.memory, eraseCondition);
// Also clear variables that reference variables to be cleared.
for (auto const& variableToClear: _variables)
for (auto const& [ref, names]: m_references)
for (auto const& [ref, names]: m_state.references)
if (names.count(variableToClear))
_variables.emplace(ref);
// Clear the value and update the reference relation.
for (auto const& name: _variables)
{
m_value.erase(name);
m_references.erase(name);
m_state.value.erase(name);
m_state.references.erase(name);
}
}
void DataFlowAnalyzer::assignValue(YulString _variable, Expression const* _value)
{
m_value[_variable] = {_value, m_loopDepth};
m_state.value[_variable] = {_value, m_loopDepth};
}
void DataFlowAnalyzer::clearKnowledgeIfInvalidated(Block const& _block)
{
SideEffectsCollector sideEffects(m_dialect, _block, &m_functionSideEffects);
if (sideEffects.invalidatesStorage())
m_storage.clear();
m_state.storage.clear();
if (sideEffects.invalidatesMemory())
m_memory.clear();
m_state.memory.clear();
}
void DataFlowAnalyzer::clearKnowledgeIfInvalidated(Expression const& _expr)
{
SideEffectsCollector sideEffects(m_dialect, _expr, &m_functionSideEffects);
if (sideEffects.invalidatesStorage())
m_storage.clear();
m_state.storage.clear();
if (sideEffects.invalidatesMemory())
m_memory.clear();
m_state.memory.clear();
}
void DataFlowAnalyzer::joinKnowledge(
@@ -353,8 +367,8 @@ void DataFlowAnalyzer::joinKnowledge(
unordered_map<YulString, YulString> const& _olderMemory
)
{
joinKnowledgeHelper(m_storage, _olderStorage);
joinKnowledgeHelper(m_memory, _olderMemory);
joinKnowledgeHelper(m_state.storage, _olderStorage);
joinKnowledgeHelper(m_state.memory, _olderMemory);
}
void DataFlowAnalyzer::joinKnowledgeHelper(
@@ -364,10 +378,10 @@ void DataFlowAnalyzer::joinKnowledgeHelper(
{
// We clear if the key does not exist in the older map or if the value is different.
// This also works for memory because _older is an "older version"
// of m_memory and thus any overlapping write would have cleared the keys
// that are not known to be different inside m_memory already.
// of m_state.memory and thus any overlapping write would have cleared the keys
// that are not known to be different inside m_state.memory already.
cxx20::erase_if(_this, mapTuple([&_older](auto&& key, auto&& currentValue){
YulString const* oldValue = valueOrNullptr(_older, key);
YulString const* oldValue = util::valueOrNullptr(_older, key);
return !oldValue || *oldValue != currentValue;
}));
}
@@ -386,8 +400,8 @@ bool DataFlowAnalyzer::inScope(YulString _variableName) const
optional<u256> DataFlowAnalyzer::valueOfIdentifier(YulString const& _name)
{
if (m_value.count(_name))
if (Literal const* literal = get_if<Literal>(m_value.at(_name).value))
if (AssignedValue const* value = variableValue(_name))
if (Literal const* literal = get_if<Literal>(value->value))
return valueOfLiteral(*literal);
return nullopt;
}
+21 -6
View File
@@ -29,6 +29,7 @@
#include <libyul/AST.h> // Needed for m_zero below.
#include <libyul/SideEffects.h>
#include <libsolutil/Numeric.h>
#include <libsolutil/Common.h>
#include <map>
@@ -38,6 +39,7 @@ namespace solidity::yul
{
struct Dialect;
struct SideEffects;
class KnowledgeBase;
/// Value assigned to a variable.
struct AssignedValue
@@ -98,6 +100,13 @@ public:
void operator()(ForLoop&) override;
void operator()(Block& _block) override;
/// @returns the current value of the given variable, if known - always movable.
AssignedValue const* variableValue(YulString _variable) const { return util::valueOrNullptr(m_state.value, _variable); }
std::set<YulString> const* references(YulString _variable) const { return util::valueOrNullptr(m_state.references, _variable); }
std::map<YulString, AssignedValue> const& allValues() const { return m_state.value; }
std::optional<YulString> storageValue(YulString _key) const;
std::optional<YulString> memoryValue(YulString _key) const;
protected:
/// Registers the assignment.
void handleAssignment(std::set<YulString> const& _names, Expression* _value, bool _isDeclaration);
@@ -164,14 +173,20 @@ protected:
/// if this is not provided or the function is not found.
std::map<YulString, SideEffects> m_functionSideEffects;
/// Current values of variables, always movable.
std::map<YulString, AssignedValue> m_value;
/// m_references[a].contains(b) <=> the current expression assigned to a references b
std::unordered_map<YulString, std::set<YulString>> m_references;
private:
struct State
{
/// Current values of variables, always movable.
std::map<YulString, AssignedValue> value;
/// m_references[a].contains(b) <=> the current expression assigned to a references b
std::unordered_map<YulString, std::set<YulString>> references;
std::unordered_map<YulString, YulString> m_storage;
std::unordered_map<YulString, YulString> m_memory;
std::unordered_map<YulString, YulString> storage;
std::unordered_map<YulString, YulString> memory;
};
State m_state;
protected:
KnowledgeBase m_knowledgeBase;
YulString m_storeFunctionName[static_cast<unsigned>(StoreLoadLocation::Last) + 1];
+2 -2
View File
@@ -54,13 +54,13 @@ void EqualStoreEliminator::visit(Statement& _statement)
{
if (auto vars = isSimpleStore(StoreLoadLocation::Storage, *expression))
{
if (auto const* currentValue = valueOrNullptr(m_storage, vars->first))
if (optional<YulString> currentValue = storageValue(vars->first))
if (*currentValue == vars->second)
m_pendingRemovals.insert(&_statement);
}
else if (auto vars = isSimpleStore(StoreLoadLocation::Memory, *expression))
{
if (auto const* currentValue = valueOrNullptr(m_memory, vars->first))
if (optional<YulString> currentValue = memoryValue(vars->first))
if (*currentValue == vars->second)
m_pendingRemovals.insert(&_statement);
}
+5 -1
View File
@@ -38,6 +38,10 @@ void ExpressionSimplifier::visit(Expression& _expression)
{
ASTModifier::visit(_expression);
while (auto const* match = SimplificationRules::findFirstMatch(_expression, m_dialect, m_value))
while (auto const* match = SimplificationRules::findFirstMatch(
_expression,
m_dialect,
[this](YulString _var) { return variableValue(_var); }
))
_expression = match->action().toExpression(debugDataOf(_expression));
}
+2 -2
View File
@@ -80,8 +80,8 @@ bool KnowledgeBase::knownToBeZero(YulString _a)
optional<u256> KnowledgeBase::valueIfKnownConstant(YulString _a)
{
if (m_variableValues.count(_a))
if (Literal const* literal = get_if<Literal>(m_variableValues.at(_a).value))
if (AssignedValue const* value = m_variableValues(_a))
if (Literal const* literal = get_if<Literal>(value->value))
return valueOfLiteral(*literal);
return {};
}
+7 -5
View File
@@ -28,6 +28,7 @@
#include <libsolutil/Numeric.h>
#include <map>
#include <functional>
namespace solidity::yul
{
@@ -37,15 +38,16 @@ struct AssignedValue;
/**
* Class that can answer questions about values of variables and their relations.
*
* The reference to the map of values provided at construction is assumed to be updating.
*/
class KnowledgeBase
{
public:
KnowledgeBase(Dialect const& _dialect, std::map<YulString, AssignedValue> const& _variableValues):
KnowledgeBase(
Dialect const& _dialect,
std::function<AssignedValue const*(YulString)> _variableValues
):
m_dialect(_dialect),
m_variableValues(_variableValues)
m_variableValues(std::move(_variableValues))
{}
bool knownToBeDifferent(YulString _a, YulString _b);
@@ -60,7 +62,7 @@ private:
Expression simplifyRecursively(Expression _expression);
Dialect const& m_dialect;
std::map<YulString, AssignedValue> const& m_variableValues;
std::function<AssignedValue const*(YulString)> m_variableValues;
size_t m_counter = 0;
};
+5 -5
View File
@@ -82,12 +82,12 @@ void LoadResolver::tryResolve(
YulString key = std::get<Identifier>(_arguments.at(0)).name;
if (_location == StoreLoadLocation::Storage)
{
if (auto value = util::valueOrNullptr(m_storage, key))
if (auto value = storageValue(key))
if (inScope(*value))
_e = Identifier{debugDataOf(_e), *value};
}
else if (!m_containsMSize && _location == StoreLoadLocation::Memory)
if (auto value = util::valueOrNullptr(m_memory, key))
if (auto value = memoryValue(key))
if (inScope(*value))
_e = Identifier{debugDataOf(_e), *value};
}
@@ -129,10 +129,10 @@ void LoadResolver::tryEvaluateKeccak(
if (costOfLiteral > costOfKeccak)
return;
auto memoryValue = util::valueOrNullptr(m_memory, memoryKey->name);
if (memoryValue && inScope(*memoryValue))
optional<YulString> value = memoryValue(memoryKey->name);
if (value && inScope(*value))
{
optional<u256> memoryContent = valueOfIdentifier(*memoryValue);
optional<u256> memoryContent = valueOfIdentifier(*value);
optional<u256> byteLength = valueOfIdentifier(length->name);
if (memoryContent && byteLength && *byteLength <= 32)
{
+12 -13
View File
@@ -79,16 +79,15 @@ void Rematerialiser::visit(Expression& _e)
{
Identifier& identifier = std::get<Identifier>(_e);
YulString name = identifier.name;
if (m_value.count(name))
if (AssignedValue const* value = variableValue(name))
{
assertThrow(m_value.at(name).value, OptimizerException, "");
AssignedValue const& value = m_value.at(name);
assertThrow(value->value, OptimizerException, "");
size_t refs = m_referenceCounts[name];
size_t cost = CodeCost::codeCost(m_dialect, *value.value);
size_t cost = CodeCost::codeCost(m_dialect, *value->value);
if (
(
!m_onlySelectedVariables && (
(refs <= 1 && value.loopDepth == m_loopDepth) ||
(refs <= 1 && value->loopDepth == m_loopDepth) ||
cost == 0 ||
(refs <= 5 && cost <= 1 && m_loopDepth == 0)
)
@@ -96,13 +95,14 @@ void Rematerialiser::visit(Expression& _e)
)
{
assertThrow(m_referenceCounts[name] > 0, OptimizerException, "");
if (ranges::all_of(m_references[name], [&](auto const& ref) { return inScope(ref); }))
auto variableReferences = references(name);
if (!variableReferences || ranges::all_of(*variableReferences, [&](auto const& ref) { return inScope(ref); }))
{
// update reference counts
m_referenceCounts[name]--;
for (auto const& ref: ReferencesCounter::countReferences(*value.value))
for (auto const& ref: ReferencesCounter::countReferences(*value->value))
m_referenceCounts[ref.first] += ref.second;
_e = (ASTCopier{}).translate(*value.value);
_e = (ASTCopier{}).translate(*value->value);
}
}
}
@@ -116,12 +116,11 @@ void LiteralRematerialiser::visit(Expression& _e)
{
Identifier& identifier = std::get<Identifier>(_e);
YulString name = identifier.name;
if (m_value.count(name))
if (AssignedValue const* value = variableValue(name))
{
Expression const* value = m_value.at(name).value;
assertThrow(value, OptimizerException, "");
if (holds_alternative<Literal>(*value))
_e = *value;
assertThrow(value->value, OptimizerException, "");
if (holds_alternative<Literal>(*value->value))
_e = *value->value;
}
}
DataFlowAnalyzer::visit(_e);
+11 -11
View File
@@ -21,15 +21,16 @@
#include <libyul/optimiser/SimplificationRules.h>
#include <libyul/optimiser/ASTCopier.h>
#include <libyul/optimiser/Semantics.h>
#include <libyul/optimiser/SyntacticalEquality.h>
#include <libyul/optimiser/DataFlowAnalyzer.h>
#include <libyul/backends/evm/EVMDialect.h>
#include <libyul/AST.h>
#include <libyul/Utilities.h>
#include <libyul/backends/evm/EVMDialect.h>
#include <libyul/optimiser/ASTCopier.h>
#include <libyul/optimiser/DataFlowAnalyzer.h>
#include <libyul/optimiser/Semantics.h>
#include <libyul/optimiser/SyntacticalEquality.h>
#include <libevmasm/RuleList.h>
#include <libsolutil/StringUtils.h>
using namespace std;
using namespace solidity;
@@ -40,7 +41,7 @@ using namespace solidity::yul;
SimplificationRules::Rule const* SimplificationRules::findFirstMatch(
Expression const& _expr,
Dialect const& _dialect,
map<YulString, AssignedValue> const& _ssaValues
function<AssignedValue const*(YulString)> const& _ssaValues
)
{
auto instruction = instructionAndArguments(_dialect, _expr);
@@ -137,7 +138,7 @@ void Pattern::setMatchGroup(unsigned _group, map<unsigned, Expression const*>& _
bool Pattern::matches(
Expression const& _expr,
Dialect const& _dialect,
map<YulString, AssignedValue> const& _ssaValues
function<AssignedValue const*(YulString)> const& _ssaValues
) const
{
Expression const* expr = &_expr;
@@ -147,8 +148,8 @@ bool Pattern::matches(
if (m_kind != PatternKind::Any && holds_alternative<Identifier>(_expr))
{
YulString varName = std::get<Identifier>(_expr).name;
if (_ssaValues.count(varName))
if (Expression const* new_expr = _ssaValues.at(varName).value)
if (AssignedValue const* value = _ssaValues(varName))
if (Expression const* new_expr = value->value)
expr = new_expr;
}
assertThrow(expr, OptimizerException, "");
@@ -249,8 +250,7 @@ Expression Pattern::toExpression(shared_ptr<DebugData const> const& _debugData)
for (auto const& arg: m_arguments)
arguments.emplace_back(arg.toExpression(_debugData));
string name = instructionInfo(m_instruction).name;
transform(begin(name), end(name), begin(name), [](auto _c) { return tolower(_c); });
string name = util::toLower(instructionInfo(m_instruction).name);
return FunctionCall{_debugData,
Identifier{_debugData, YulString{name}},
+2 -2
View File
@@ -62,7 +62,7 @@ public:
static Rule const* findFirstMatch(
Expression const& _expr,
Dialect const& _dialect,
std::map<YulString, AssignedValue> const& _ssaValues
std::function<AssignedValue const*(YulString)> const& _ssaValues
);
/// Checks whether the rulelist is non-empty. This is usually enforced
@@ -119,7 +119,7 @@ public:
bool matches(
Expression const& _expr,
Dialect const& _dialect,
std::map<YulString, AssignedValue> const& _ssaValues
std::function<AssignedValue const*(YulString)> const& _ssaValues
) const;
std::vector<Pattern> arguments() const { return m_arguments; }
+5 -4
View File
@@ -67,7 +67,8 @@ public:
if (size_t const* cost = util::valueOrNullptr(m_expressionCodeCost, candidate))
{
size_t numRef = m_numReferences[candidate];
cand[*cost * numRef].emplace_back(candidate, m_references[candidate]);
set<YulString> const* ref = references(candidate);
cand[*cost * numRef].emplace_back(candidate, ref ? move(*ref) : set<YulString>{});
}
}
return cand;
@@ -80,11 +81,11 @@ public:
if (_varDecl.variables.size() == 1)
{
YulString varName = _varDecl.variables.front().name;
if (m_value.count(varName))
if (AssignedValue const* value = variableValue(varName))
{
yulAssert(!m_expressionCodeCost.count(varName), "");
m_candidates.emplace_back(varName);
m_expressionCodeCost[varName] = CodeCost::codeCost(m_dialect, *m_value[varName].value);
m_expressionCodeCost[varName] = CodeCost::codeCost(m_dialect, *value->value);
}
}
}
@@ -105,7 +106,7 @@ public:
YulString name = std::get<Identifier>(_e).name;
if (m_expressionCodeCost.count(name))
{
if (!m_value.count(name))
if (!variableValue(name))
rematImpossible(name);
else
++m_numReferences[name];
+24 -24
View File
@@ -55,6 +55,30 @@ OptionalStatements replaceConstArgSwitch(Switch& _switchStmt, u256 const& _const
return optional<vector<Statement>>{vector<Statement>{}};
}
optional<u256> hasLiteralValue(Expression const& _expression)
{
if (holds_alternative<Literal>(_expression))
return valueOfLiteral(std::get<Literal>(_expression));
else
return std::optional<u256>();
}
bool expressionAlwaysTrue(Expression const& _expression)
{
if (std::optional<u256> value = hasLiteralValue(_expression))
return *value != 0;
else
return false;
}
bool expressionAlwaysFalse(Expression const& _expression)
{
if (std::optional<u256> value = hasLiteralValue(_expression))
return *value == 0;
else
return false;
}
}
void StructuralSimplifier::run(OptimiserStepContext&, Block& _ast)
@@ -103,27 +127,3 @@ void StructuralSimplifier::simplify(std::vector<yul::Statement>& _statements)
}
);
}
bool StructuralSimplifier::expressionAlwaysTrue(Expression const& _expression)
{
if (std::optional<u256> value = hasLiteralValue(_expression))
return *value != 0;
else
return false;
}
bool StructuralSimplifier::expressionAlwaysFalse(Expression const& _expression)
{
if (std::optional<u256> value = hasLiteralValue(_expression))
return *value == 0;
else
return false;
}
std::optional<u256> StructuralSimplifier::hasLiteralValue(Expression const& _expression) const
{
if (holds_alternative<Literal>(_expression))
return valueOfLiteral(std::get<Literal>(_expression));
else
return std::optional<u256>();
}
-4
View File
@@ -18,7 +18,6 @@
#pragma once
#include <libyul/optimiser/ASTWalker.h>
#include <libyul/optimiser/DataFlowAnalyzer.h>
#include <libyul/optimiser/OptimiserStep.h>
#include <libsolutil/Common.h>
@@ -50,9 +49,6 @@ private:
StructuralSimplifier() = default;
void simplify(std::vector<Statement>& _statements);
bool expressionAlwaysTrue(Expression const& _expression);
bool expressionAlwaysFalse(Expression const& _expression);
std::optional<u256> hasLiteralValue(Expression const& _expression) const;
};
}
+3
View File
@@ -57,6 +57,7 @@
#include <libyul/optimiser/StructuralSimplifier.h>
#include <libyul/optimiser/SyntacticalEquality.h>
#include <libyul/optimiser/UnusedAssignEliminator.h>
#include <libyul/optimiser/UnusedStoreEliminator.h>
#include <libyul/optimiser/VarNameCleaner.h>
#include <libyul/optimiser/LoadResolver.h>
#include <libyul/optimiser/LoopInvariantCodeMotion.h>
@@ -222,6 +223,7 @@ map<string, unique_ptr<OptimiserStep>> const& OptimiserSuite::allSteps()
LoadResolver,
LoopInvariantCodeMotion,
UnusedAssignEliminator,
UnusedStoreEliminator,
ReasoningBasedSimplifier,
Rematerialiser,
SSAReverser,
@@ -264,6 +266,7 @@ map<string, char> const& OptimiserSuite::stepNameToAbbreviationMap()
{LoopInvariantCodeMotion::name, 'M'},
{ReasoningBasedSimplifier::name, 'R'},
{UnusedAssignEliminator::name, 'r'},
{UnusedStoreEliminator::name, 'S'},
{Rematerialiser::name, 'm'},
{SSAReverser::name, 'V'},
{SSATransform::name, 'a'},
+379
View File
@@ -0,0 +1,379 @@
/*
This file is part of solidity.
solidity is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
solidity is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
// SPDX-License-Identifier: GPL-3.0
/**
* Optimiser component that removes stores to memory and storage slots that are not used
* or overwritten later on.
*/
#include <libyul/optimiser/UnusedStoreEliminator.h>
#include <libyul/optimiser/Semantics.h>
#include <libyul/optimiser/OptimizerUtilities.h>
#include <libyul/optimiser/Semantics.h>
#include <libyul/optimiser/SSAValueTracker.h>
#include <libyul/optimiser/DataFlowAnalyzer.h>
#include <libyul/optimiser/KnowledgeBase.h>
#include <libyul/ControlFlowSideEffectsCollector.h>
#include <libyul/AST.h>
#include <libsolutil/CommonData.h>
#include <libevmasm/Instruction.h>
#include <libevmasm/SemanticInformation.h>
#include <range/v3/algorithm/all_of.hpp>
using namespace std;
using namespace solidity;
using namespace solidity::yul;
/// Variable names for special constants that can never appear in actual Yul code.
static string const zero{"@ 0"};
static string const one{"@ 1"};
static string const thirtyTwo{"@ 32"};
void UnusedStoreEliminator::run(OptimiserStepContext& _context, Block& _ast)
{
map<YulString, SideEffects> functionSideEffects = SideEffectsPropagator::sideEffects(
_context.dialect,
CallGraphGenerator::callGraph(_ast)
);
SSAValueTracker ssaValues;
ssaValues(_ast);
map<YulString, AssignedValue> values;
for (auto const& [name, expression]: ssaValues.values())
values[name] = AssignedValue{expression, {}};
Expression const zeroLiteral{Literal{{}, LiteralKind::Number, YulString{"0"}, {}}};
Expression const oneLiteral{Literal{{}, LiteralKind::Number, YulString{"1"}, {}}};
Expression const thirtyTwoLiteral{Literal{{}, LiteralKind::Number, YulString{"32"}, {}}};
values[YulString{zero}] = AssignedValue{&zeroLiteral, {}};
values[YulString{one}] = AssignedValue{&oneLiteral, {}};
values[YulString{thirtyTwo}] = AssignedValue{&thirtyTwoLiteral, {}};
bool const ignoreMemory = MSizeFinder::containsMSize(_context.dialect, _ast);
UnusedStoreEliminator rse{
_context.dialect,
functionSideEffects,
ControlFlowSideEffectsCollector{_context.dialect, _ast}.functionSideEffectsNamed(),
values,
ignoreMemory
};
rse(_ast);
rse.changeUndecidedTo(State::Unused, Location::Memory);
rse.changeUndecidedTo(State::Used, Location::Storage);
rse.scheduleUnusedForDeletion();
StatementRemover remover(rse.m_pendingRemovals);
remover(_ast);
}
void UnusedStoreEliminator::operator()(FunctionCall const& _functionCall)
{
UnusedStoreBase::operator()(_functionCall);
for (Operation const& op: operationsFromFunctionCall(_functionCall))
applyOperation(op);
ControlFlowSideEffects sideEffects;
if (auto builtin = m_dialect.builtin(_functionCall.functionName.name))
sideEffects = builtin->controlFlowSideEffects;
else
sideEffects = m_controlFlowSideEffects.at(_functionCall.functionName.name);
if (!sideEffects.canContinue)
{
changeUndecidedTo(State::Unused, Location::Memory);
changeUndecidedTo(sideEffects.canTerminate ? State::Used : State::Unused, Location::Storage);
}
}
void UnusedStoreEliminator::operator()(FunctionDefinition const& _functionDefinition)
{
ScopedSaveAndRestore storeOperations(m_storeOperations, {});
UnusedStoreBase::operator()(_functionDefinition);
}
void UnusedStoreEliminator::operator()(Leave const&)
{
changeUndecidedTo(State::Used);
}
void UnusedStoreEliminator::visit(Statement const& _statement)
{
using evmasm::Instruction;
UnusedStoreBase::visit(_statement);
auto const* exprStatement = get_if<ExpressionStatement>(&_statement);
if (!exprStatement)
return;
FunctionCall const* funCall = get_if<FunctionCall>(&exprStatement->expression);
yulAssert(funCall);
optional<Instruction> instruction = toEVMInstruction(m_dialect, funCall->functionName.name);
if (!instruction)
return;
if (!ranges::all_of(funCall->arguments, [](Expression const& _expr) -> bool {
return get_if<Identifier>(&_expr) || get_if<Literal>(&_expr);
}))
return;
// We determine if this is a store instruction without additional side-effects
// both by querying a combination of semantic information and by listing the instructions.
// This way the assert below should be triggered on any change.
using evmasm::SemanticInformation;
bool isStorageWrite = (*instruction == Instruction::SSTORE);
bool isMemoryWrite =
*instruction == Instruction::EXTCODECOPY ||
*instruction == Instruction::CODECOPY ||
*instruction == Instruction::CALLDATACOPY ||
*instruction == Instruction::RETURNDATACOPY ||
*instruction == Instruction::MSTORE ||
*instruction == Instruction::MSTORE8;
bool isCandidateForRemoval =
SemanticInformation::otherState(*instruction) != SemanticInformation::Write && (
SemanticInformation::storage(*instruction) == SemanticInformation::Write ||
(!m_ignoreMemory && SemanticInformation::memory(*instruction) == SemanticInformation::Write)
);
yulAssert(isCandidateForRemoval == (isStorageWrite || (!m_ignoreMemory && isMemoryWrite)));
if (isCandidateForRemoval)
{
m_stores[YulString{}].insert({&_statement, State::Undecided});
vector<Operation> operations = operationsFromFunctionCall(*funCall);
yulAssert(operations.size() == 1, "");
m_storeOperations[&_statement] = move(operations.front());
}
}
void UnusedStoreEliminator::finalizeFunctionDefinition(FunctionDefinition const&)
{
changeUndecidedTo(State::Used);
scheduleUnusedForDeletion();
}
vector<UnusedStoreEliminator::Operation> UnusedStoreEliminator::operationsFromFunctionCall(
FunctionCall const& _functionCall
) const
{
using evmasm::Instruction;
YulString functionName = _functionCall.functionName.name;
SideEffects sideEffects;
if (BuiltinFunction const* f = m_dialect.builtin(functionName))
sideEffects = f->sideEffects;
else
sideEffects = m_functionSideEffects.at(functionName);
optional<Instruction> instruction = toEVMInstruction(m_dialect, functionName);
if (!instruction)
{
vector<Operation> result;
// Unknown read is worse than unknown write.
if (sideEffects.memory != SideEffects::Effect::None)
result.emplace_back(Operation{Location::Memory, Effect::Read, {}, {}});
if (sideEffects.storage != SideEffects::Effect::None)
result.emplace_back(Operation{Location::Storage, Effect::Read, {}, {}});
return result;
}
using evmasm::SemanticInformation;
return util::applyMap(
SemanticInformation::readWriteOperations(*instruction),
[&](SemanticInformation::Operation const& _op) -> Operation
{
yulAssert(!(_op.lengthParameter && _op.lengthConstant));
yulAssert(_op.effect != Effect::None);
Operation ourOp{_op.location, _op.effect, {}, {}};
if (_op.startParameter)
ourOp.start = identifierNameIfSSA(_functionCall.arguments.at(*_op.startParameter));
if (_op.lengthParameter)
ourOp.length = identifierNameIfSSA(_functionCall.arguments.at(*_op.lengthParameter));
if (_op.lengthConstant)
switch (*_op.lengthConstant)
{
case 1: ourOp.length = YulString(one); break;
case 32: ourOp.length = YulString(thirtyTwo); break;
default: yulAssert(false);
}
return ourOp;
}
);
}
void UnusedStoreEliminator::applyOperation(UnusedStoreEliminator::Operation const& _operation)
{
for (auto& [statement, state]: m_stores[YulString{}])
if (state == State::Undecided)
{
Operation const& storeOperation = m_storeOperations.at(statement);
if (_operation.effect == Effect::Read && !knownUnrelated(storeOperation, _operation))
state = State::Used;
else if (_operation.effect == Effect::Write && knownCovered(storeOperation, _operation))
state = State::Unused;
}
}
bool UnusedStoreEliminator::knownUnrelated(
UnusedStoreEliminator::Operation const& _op1,
UnusedStoreEliminator::Operation const& _op2
) const
{
KnowledgeBase knowledge(m_dialect, [this](YulString _var) { return util::valueOrNullptr(m_ssaValues, _var); });
if (_op1.location != _op2.location)
return true;
if (_op1.location == Location::Storage)
{
if (_op1.start && _op2.start)
{
yulAssert(
_op1.length &&
_op2.length &&
knowledge.valueIfKnownConstant(*_op1.length) == 1 &&
knowledge.valueIfKnownConstant(*_op2.length) == 1
);
return knowledge.knownToBeDifferent(*_op1.start, *_op2.start);
}
}
else
{
yulAssert(_op1.location == Location::Memory, "");
if (
(_op1.length && knowledge.knownToBeZero(*_op1.length)) ||
(_op2.length && knowledge.knownToBeZero(*_op2.length))
)
return true;
if (_op1.start && _op1.length && _op2.start)
{
optional<u256> length1 = knowledge.valueIfKnownConstant(*_op1.length);
optional<u256> start1 = knowledge.valueIfKnownConstant(*_op1.start);
optional<u256> start2 = knowledge.valueIfKnownConstant(*_op2.start);
if (
(length1 && start1 && start2) &&
*start1 + *length1 >= *start1 && // no overflow
*start1 + *length1 <= *start2
)
return true;
}
if (_op2.start && _op2.length && _op1.start)
{
optional<u256> length2 = knowledge.valueIfKnownConstant(*_op2.length);
optional<u256> start2 = knowledge.valueIfKnownConstant(*_op2.start);
optional<u256> start1 = knowledge.valueIfKnownConstant(*_op1.start);
if (
(length2 && start2 && start1) &&
*start2 + *length2 >= *start2 && // no overflow
*start2 + *length2 <= *start1
)
return true;
}
if (_op1.start && _op1.length && _op2.start && _op2.length)
{
optional<u256> length1 = knowledge.valueIfKnownConstant(*_op1.length);
optional<u256> length2 = knowledge.valueIfKnownConstant(*_op2.length);
if (
(length1 && *length1 <= 32) &&
(length2 && *length2 <= 32) &&
knowledge.knownToBeDifferentByAtLeast32(*_op1.start, *_op2.start)
)
return true;
}
}
return false;
}
bool UnusedStoreEliminator::knownCovered(
UnusedStoreEliminator::Operation const& _covered,
UnusedStoreEliminator::Operation const& _covering
) const
{
if (_covered.location != _covering.location)
return false;
if (
(_covered.start && _covered.start == _covering.start) &&
(_covered.length && _covered.length == _covering.length)
)
return true;
if (_covered.location == Location::Memory)
{
KnowledgeBase knowledge(m_dialect, [this](YulString _var) { return util::valueOrNullptr(m_ssaValues, _var); });
if (_covered.length && knowledge.knownToBeZero(*_covered.length))
return true;
// Condition (i = cover_i_ng, e = cover_e_d):
// i.start <= e.start && e.start + e.length <= i.start + i.length
if (!_covered.start || !_covering.start || !_covered.length || !_covering.length)
return false;
optional<u256> coveredLength = knowledge.valueIfKnownConstant(*_covered.length);
optional<u256> coveringLength = knowledge.valueIfKnownConstant(*_covering.length);
if (knowledge.knownToBeEqual(*_covered.start, *_covering.start))
if (coveredLength && coveringLength && *coveredLength <= *coveringLength)
return true;
optional<u256> coveredStart = knowledge.valueIfKnownConstant(*_covered.start);
optional<u256> coveringStart = knowledge.valueIfKnownConstant(*_covering.start);
if (coveredStart && coveringStart && coveredLength && coveringLength)
if (
*coveringStart <= *coveredStart &&
*coveringStart + *coveringLength >= *coveringStart && // no overflow
*coveredStart + *coveredLength >= *coveredStart && // no overflow
*coveredStart + *coveredLength <= *coveringStart + *coveringLength
)
return true;
// TODO for this we probably need a non-overflow assumption as above.
// Condition (i = cover_i_ng, e = cover_e_d):
// i.start <= e.start && e.start + e.length <= i.start + i.length
}
return false;
}
void UnusedStoreEliminator::changeUndecidedTo(
State _newState,
optional<UnusedStoreEliminator::Location> _onlyLocation)
{
for (auto& [statement, state]: m_stores[YulString{}])
if (
state == State::Undecided &&
(_onlyLocation == nullopt || *_onlyLocation == m_storeOperations.at(statement).location)
)
state = _newState;
}
optional<YulString> UnusedStoreEliminator::identifierNameIfSSA(Expression const& _expression) const
{
if (Identifier const* identifier = get_if<Identifier>(&_expression))
if (m_ssaValues.count(identifier->name))
return {identifier->name};
return nullopt;
}
void UnusedStoreEliminator::scheduleUnusedForDeletion()
{
for (auto const& [statement, state]: m_stores[YulString{}])
if (state == State::Unused)
m_pendingRemovals.insert(statement);
}
+119
View File
@@ -0,0 +1,119 @@
/*
This file is part of solidity.
solidity is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
solidity is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
// SPDX-License-Identifier: GPL-3.0
/**
* Optimiser component that removes stores to memory and storage slots that are not used
* or overwritten later on.
*/
#pragma once
#include <libyul/ASTForward.h>
#include <libyul/optimiser/ASTWalker.h>
#include <libyul/optimiser/OptimiserStep.h>
#include <libyul/optimiser/Semantics.h>
#include <libyul/optimiser/UnusedStoreBase.h>
#include <libevmasm/SemanticInformation.h>
#include <map>
#include <vector>
namespace solidity::yul
{
struct Dialect;
struct AssignedValue;
/**
* Optimizer component that removes sstore statements if they
* are overwritten in all code paths or never read from.
*
* The m_store member of UnusedStoreBase is only used with the empty yul string
* as key in the first dimension.
*
* Best run in SSA form.
*
* Prerequisite: Disambiguator, ForLoopInitRewriter.
*/
class UnusedStoreEliminator: public UnusedStoreBase
{
public:
static constexpr char const* name{"UnusedStoreEliminator"};
static void run(OptimiserStepContext& _context, Block& _ast);
explicit UnusedStoreEliminator(
Dialect const& _dialect,
std::map<YulString, SideEffects> const& _functionSideEffects,
std::map<YulString, ControlFlowSideEffects> _controlFlowSideEffects,
std::map<YulString, AssignedValue> const& _ssaValues,
bool _ignoreMemory
):
UnusedStoreBase(_dialect),
m_ignoreMemory(_ignoreMemory),
m_functionSideEffects(_functionSideEffects),
m_controlFlowSideEffects(_controlFlowSideEffects),
m_ssaValues(_ssaValues)
{}
using UnusedStoreBase::operator();
void operator()(FunctionCall const& _functionCall) override;
void operator()(FunctionDefinition const&) override;
void operator()(Leave const&) override;
using UnusedStoreBase::visit;
void visit(Statement const& _statement) override;
using Location = evmasm::SemanticInformation::Location;
using Effect = evmasm::SemanticInformation::Effect;
struct Operation
{
Location location;
Effect effect;
/// Start of affected area. Unknown if not provided.
std::optional<YulString> start;
/// Length of affected area, unknown if not provided.
/// Unused for storage.
std::optional<YulString> length;
};
private:
void shortcutNestedLoop(TrackedStores const&) override
{
// We might only need to do this for newly introduced stores in the loop.
changeUndecidedTo(State::Used);
}
void finalizeFunctionDefinition(FunctionDefinition const&) override;
std::vector<Operation> operationsFromFunctionCall(FunctionCall const& _functionCall) const;
void applyOperation(Operation const& _operation);
bool knownUnrelated(Operation const& _op1, Operation const& _op2) const;
bool knownCovered(Operation const& _covered, Operation const& _covering) const;
void changeUndecidedTo(State _newState, std::optional<Location> _onlyLocation = std::nullopt);
void scheduleUnusedForDeletion();
std::optional<YulString> identifierNameIfSSA(Expression const& _expression) const;
bool const m_ignoreMemory;
std::map<YulString, SideEffects> const& m_functionSideEffects;
std::map<YulString, ControlFlowSideEffects> m_controlFlowSideEffects;
std::map<YulString, AssignedValue> const& m_ssaValues;
std::map<Statement const*, Operation> m_storeOperations;
};
}