mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Merge remote-tracking branch 'origin/develop' into breaking
This commit is contained in:
@@ -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; }
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
@@ -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()))
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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())};
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {};
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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 {};
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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}},
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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>();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -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'},
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user