mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Re-implement unused assign / unused store eliminator.
This commit is contained in:
parent
960889f532
commit
fac5666dc9
@ -6,6 +6,7 @@ Language Features:
|
|||||||
Compiler Features:
|
Compiler Features:
|
||||||
* SMTChecker: Properties that are proved safe are now reported explicitly at the end of the analysis. By default, only the number of safe properties is shown. The CLI option ``--model-checker-show-proved-safe`` and the JSON option ``settings.modelChecker.showProvedSafe`` can be enabled to show the full list of safe properties.
|
* SMTChecker: Properties that are proved safe are now reported explicitly at the end of the analysis. By default, only the number of safe properties is shown. The CLI option ``--model-checker-show-proved-safe`` and the JSON option ``settings.modelChecker.showProvedSafe`` can be enabled to show the full list of safe properties.
|
||||||
* SMTChecker: Group all messages about unsupported language features in a single warning. The CLI option ``--model-checker-show-unsupported`` and the JSON option ``settings.modelChecker.showUnsupported`` can be enabled to show the full list.
|
* SMTChecker: Group all messages about unsupported language features in a single warning. The CLI option ``--model-checker-show-unsupported`` and the JSON option ``settings.modelChecker.showUnsupported`` can be enabled to show the full list.
|
||||||
|
* Optimizer: Re-implement simplified version of UnusedAssignEliminator and UnusedStoreEliminator. It can correctly remove some unused assignments in deeply nested loops that were ignored by the old version.
|
||||||
|
|
||||||
|
|
||||||
Bugfixes:
|
Bugfixes:
|
||||||
|
@ -24,48 +24,49 @@
|
|||||||
|
|
||||||
#include <libyul/optimiser/Semantics.h>
|
#include <libyul/optimiser/Semantics.h>
|
||||||
#include <libyul/optimiser/OptimizerUtilities.h>
|
#include <libyul/optimiser/OptimizerUtilities.h>
|
||||||
|
#include <libyul/ControlFlowSideEffectsCollector.h>
|
||||||
#include <libyul/AST.h>
|
#include <libyul/AST.h>
|
||||||
|
#include <libyul/AsmPrinter.h>
|
||||||
|
|
||||||
#include <libsolutil/CommonData.h>
|
#include <libsolutil/CommonData.h>
|
||||||
|
|
||||||
#include <range/v3/action/remove_if.hpp>
|
#include <range/v3/action/remove_if.hpp>
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
using namespace solidity;
|
using namespace solidity;
|
||||||
using namespace solidity::yul;
|
using namespace solidity::yul;
|
||||||
|
|
||||||
void UnusedAssignEliminator::run(OptimiserStepContext& _context, Block& _ast)
|
void UnusedAssignEliminator::run(OptimiserStepContext& _context, Block& _ast)
|
||||||
{
|
{
|
||||||
UnusedAssignEliminator rae{_context.dialect};
|
UnusedAssignEliminator uae{
|
||||||
rae(_ast);
|
_context.dialect,
|
||||||
|
ControlFlowSideEffectsCollector{_context.dialect, _ast}.functionSideEffectsNamed()
|
||||||
|
};
|
||||||
|
uae(_ast);
|
||||||
|
|
||||||
StatementRemover remover{rae.m_pendingRemovals};
|
uae.m_storesToRemove += uae.m_allStores - uae.m_usedStores;
|
||||||
|
|
||||||
|
set<Statement const*> toRemove{uae.m_storesToRemove.begin(), uae.m_storesToRemove.end()};
|
||||||
|
StatementRemover remover{toRemove};
|
||||||
remover(_ast);
|
remover(_ast);
|
||||||
}
|
}
|
||||||
|
|
||||||
void UnusedAssignEliminator::operator()(Identifier const& _identifier)
|
void UnusedAssignEliminator::operator()(Identifier const& _identifier)
|
||||||
{
|
{
|
||||||
changeUndecidedTo(_identifier.name, State::Used);
|
markUsed(_identifier.name);
|
||||||
}
|
|
||||||
|
|
||||||
void UnusedAssignEliminator::operator()(VariableDeclaration const& _variableDeclaration)
|
|
||||||
{
|
|
||||||
UnusedStoreBase::operator()(_variableDeclaration);
|
|
||||||
|
|
||||||
for (auto const& var: _variableDeclaration.variables)
|
|
||||||
m_declaredVariables.emplace(var.name);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void UnusedAssignEliminator::operator()(Assignment const& _assignment)
|
void UnusedAssignEliminator::operator()(Assignment const& _assignment)
|
||||||
{
|
{
|
||||||
visit(*_assignment.value);
|
visit(*_assignment.value);
|
||||||
for (auto const& var: _assignment.variableNames)
|
// Do not visit the variables because they are Identifiers
|
||||||
changeUndecidedTo(var.name, State::Unused);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void UnusedAssignEliminator::operator()(FunctionDefinition const& _functionDefinition)
|
void UnusedAssignEliminator::operator()(FunctionDefinition const& _functionDefinition)
|
||||||
{
|
{
|
||||||
ScopedSaveAndRestore outerDeclaredVariables(m_declaredVariables, {});
|
|
||||||
ScopedSaveAndRestore outerReturnVariables(m_returnVariables, {});
|
ScopedSaveAndRestore outerReturnVariables(m_returnVariables, {});
|
||||||
|
|
||||||
for (auto const& retParam: _functionDefinition.returnVariables)
|
for (auto const& retParam: _functionDefinition.returnVariables)
|
||||||
@ -74,20 +75,37 @@ void UnusedAssignEliminator::operator()(FunctionDefinition const& _functionDefin
|
|||||||
UnusedStoreBase::operator()(_functionDefinition);
|
UnusedStoreBase::operator()(_functionDefinition);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void UnusedAssignEliminator::operator()(FunctionCall const& _functionCall)
|
||||||
|
{
|
||||||
|
UnusedStoreBase::operator()(_functionCall);
|
||||||
|
|
||||||
|
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)
|
||||||
|
// We do not return from the current function, so it is OK to also
|
||||||
|
// clear the return variables.
|
||||||
|
m_activeStores.clear();
|
||||||
|
}
|
||||||
|
|
||||||
void UnusedAssignEliminator::operator()(Leave const&)
|
void UnusedAssignEliminator::operator()(Leave const&)
|
||||||
{
|
{
|
||||||
for (YulString name: m_returnVariables)
|
for (YulString name: m_returnVariables)
|
||||||
changeUndecidedTo(name, State::Used);
|
markUsed(name);
|
||||||
|
m_activeStores.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
void UnusedAssignEliminator::operator()(Block const& _block)
|
void UnusedAssignEliminator::operator()(Block const& _block)
|
||||||
{
|
{
|
||||||
ScopedSaveAndRestore outerDeclaredVariables(m_declaredVariables, {});
|
|
||||||
|
|
||||||
UnusedStoreBase::operator()(_block);
|
UnusedStoreBase::operator()(_block);
|
||||||
|
|
||||||
for (auto const& var: m_declaredVariables)
|
for (auto const& statement: _block.statements)
|
||||||
finalize(var, State::Unused);
|
if (auto const* varDecl = get_if<VariableDeclaration>(&statement))
|
||||||
|
for (auto const& var: varDecl->variables)
|
||||||
|
m_activeStores.erase(var.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
void UnusedAssignEliminator::visit(Statement const& _statement)
|
void UnusedAssignEliminator::visit(Statement const& _statement)
|
||||||
@ -95,63 +113,49 @@ void UnusedAssignEliminator::visit(Statement const& _statement)
|
|||||||
UnusedStoreBase::visit(_statement);
|
UnusedStoreBase::visit(_statement);
|
||||||
|
|
||||||
if (auto const* assignment = get_if<Assignment>(&_statement))
|
if (auto const* assignment = get_if<Assignment>(&_statement))
|
||||||
if (assignment->variableNames.size() == 1)
|
{
|
||||||
// Default-construct it in "Undecided" state if it does not yet exist.
|
// We do not remove assignments whose values might have side-effects,
|
||||||
m_stores[assignment->variableNames.front().name][&_statement];
|
// but clear the active stores to the assigned variables in any case.
|
||||||
|
if (SideEffectsCollector{m_dialect, *assignment->value}.movable())
|
||||||
|
{
|
||||||
|
m_allStores.insert(&_statement);
|
||||||
|
for (auto const& var: assignment->variableNames)
|
||||||
|
m_activeStores[var.name] = {&_statement};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
for (auto const& var: assignment->variableNames)
|
||||||
|
m_activeStores[var.name].clear();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void UnusedAssignEliminator::shortcutNestedLoop(TrackedStores const& _zeroRuns)
|
void UnusedAssignEliminator::shortcutNestedLoop(ActiveStores const& _zeroRuns)
|
||||||
{
|
{
|
||||||
// Shortcut to avoid horrible runtime:
|
// Shortcut to avoid horrible runtime:
|
||||||
// Change all assignments that were newly introduced in the for loop to "used".
|
// Change all assignments that were newly introduced in the for loop to "used".
|
||||||
// We do not have to do that with the "break" or "continue" paths, because
|
// We do not have to do that with the "break" or "continue" paths, because
|
||||||
// they will be joined later anyway.
|
// they will be joined later anyway.
|
||||||
// TODO parallel traversal might be more efficient here.
|
|
||||||
for (auto& [variable, stores]: m_stores)
|
for (auto& [variable, stores]: m_activeStores)
|
||||||
for (auto& assignment: stores)
|
|
||||||
{
|
{
|
||||||
auto zeroIt = _zeroRuns.find(variable);
|
auto zeroIt = _zeroRuns.find(variable);
|
||||||
if (zeroIt != _zeroRuns.end() && zeroIt->second.count(assignment.first))
|
for (auto& assignment: stores)
|
||||||
|
{
|
||||||
|
if (zeroIt != _zeroRuns.end() && zeroIt->second.count(assignment))
|
||||||
continue;
|
continue;
|
||||||
assignment.second = State::Value::Used;
|
m_usedStores.insert(assignment);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void UnusedAssignEliminator::finalizeFunctionDefinition(FunctionDefinition const& _functionDefinition)
|
void UnusedAssignEliminator::finalizeFunctionDefinition(FunctionDefinition const& _functionDefinition)
|
||||||
{
|
{
|
||||||
for (auto const& param: _functionDefinition.parameters)
|
|
||||||
finalize(param.name, State::Unused);
|
|
||||||
for (auto const& retParam: _functionDefinition.returnVariables)
|
for (auto const& retParam: _functionDefinition.returnVariables)
|
||||||
finalize(retParam.name, State::Used);
|
markUsed(retParam.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
void UnusedAssignEliminator::changeUndecidedTo(YulString _variable, UnusedAssignEliminator::State _newState)
|
void UnusedAssignEliminator::markUsed(YulString _variable)
|
||||||
{
|
{
|
||||||
for (auto& assignment: m_stores[_variable])
|
for (auto& assignment: m_activeStores[_variable])
|
||||||
if (assignment.second == State::Undecided)
|
m_usedStores.insert(assignment);
|
||||||
assignment.second = _newState;
|
m_activeStores.erase(_variable);
|
||||||
}
|
|
||||||
|
|
||||||
void UnusedAssignEliminator::finalize(YulString _variable, UnusedAssignEliminator::State _finalState)
|
|
||||||
{
|
|
||||||
std::map<Statement const*, State> stores = std::move(m_stores[_variable]);
|
|
||||||
m_stores.erase(_variable);
|
|
||||||
|
|
||||||
for (auto& breakAssignments: m_forLoopInfo.pendingBreakStmts)
|
|
||||||
{
|
|
||||||
util::joinMap(stores, std::move(breakAssignments[_variable]), State::join);
|
|
||||||
breakAssignments.erase(_variable);
|
|
||||||
}
|
|
||||||
for (auto& continueAssignments: m_forLoopInfo.pendingContinueStmts)
|
|
||||||
{
|
|
||||||
util::joinMap(stores, std::move(continueAssignments[_variable]), State::join);
|
|
||||||
continueAssignments.erase(_variable);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto&& [statement, state]: stores)
|
|
||||||
if (
|
|
||||||
(state == State::Unused || (state == State::Undecided && _finalState == State::Unused)) &&
|
|
||||||
SideEffectsCollector{m_dialect, *std::get<Assignment>(*statement).value}.movable()
|
|
||||||
)
|
|
||||||
m_pendingRemovals.insert(statement);
|
|
||||||
}
|
}
|
||||||
|
@ -26,6 +26,7 @@
|
|||||||
#include <libyul/optimiser/ASTWalker.h>
|
#include <libyul/optimiser/ASTWalker.h>
|
||||||
#include <libyul/optimiser/OptimiserStep.h>
|
#include <libyul/optimiser/OptimiserStep.h>
|
||||||
#include <libyul/optimiser/UnusedStoreBase.h>
|
#include <libyul/optimiser/UnusedStoreBase.h>
|
||||||
|
#include <libyul/optimiser/Semantics.h>
|
||||||
|
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
@ -62,28 +63,34 @@ struct Dialect;
|
|||||||
* Detailed rules:
|
* Detailed rules:
|
||||||
*
|
*
|
||||||
* The AST is traversed twice: in an information gathering step and in the
|
* The AST is traversed twice: in an information gathering step and in the
|
||||||
* actual removal step. During information gathering, we maintain a
|
* actual removal step. During information gathering, assignment statements
|
||||||
* mapping from assignment statements to the three states
|
* can be marked as "potentially unused" or as "used".
|
||||||
* "unused", "undecided" and "used".
|
*
|
||||||
* When an assignment is visited, it is added to the mapping in the "undecided" state
|
* When an assignment is visited, it is stored in the "set of all stores" and
|
||||||
* (see remark about for loops below) and every other assignment to the same variable
|
* added to the branch-dependent "active" sets for the assigned variables. This active
|
||||||
* that is still in the "undecided" state is changed to "unused".
|
* set for a variable contains all statements where that variable was last assigned to, i.e.
|
||||||
* When a variable is referenced, the state of any assignment to that variable still
|
* where a read from that variable could read from.
|
||||||
* in the "undecided" state is changed to "used".
|
* Furthermore, all other active sets for the assigned variables are cleared.
|
||||||
* At points where control flow splits, a copy
|
*
|
||||||
* of the mapping is handed over to each branch. At points where control flow
|
* When a reference to a variable is visited, the active assignments to that variable
|
||||||
* joins, the two mappings coming from the two branches are combined in the following way:
|
* in the current branch are marked as "used". This mark is permanent.
|
||||||
* Statements that are only in one mapping or have the same state are used unchanged.
|
* Also, the active set for this variable in the current branch is cleared.
|
||||||
* Conflicting values are resolved in the following way:
|
*
|
||||||
* "unused", "undecided" -> "undecided"
|
* At points where control-flow splits, we maintain a copy of the active set
|
||||||
* "unused", "used" -> "used"
|
* (all other data structures are shared across branches).
|
||||||
* "undecided, "used" -> "used".
|
*
|
||||||
|
* At control-flow joins, we combine the sets of active stores for each variable.
|
||||||
|
*
|
||||||
|
* In the example above, the active set right after the assignment "b := mload(a)" (but before
|
||||||
|
* the control-flow join) is "b := mload(a)"; the assignment "b := 2" was removed.
|
||||||
|
* After the control-flow join it will contain both "b := mload(a)" and "b := 2", coming from
|
||||||
|
* the two branches.
|
||||||
*
|
*
|
||||||
* For for-loops, the condition, body and post-part are visited twice, taking
|
* For for-loops, the condition, body and post-part are visited twice, taking
|
||||||
* the joining control-flow at the condition into account.
|
* the joining control-flow at the condition into account.
|
||||||
* In other words, we create three control flow paths: Zero runs of the loop,
|
* In other words, we create three control flow paths: Zero runs of the loop,
|
||||||
* one run and two runs and then combine them at the end.
|
* one run and two runs and then combine them at the end.
|
||||||
* Running at most twice is enough because there are only three different states.
|
* Running at most twice is enough because this takes into account all possible control-flow connections.
|
||||||
*
|
*
|
||||||
* Since this algorithm has exponential runtime in the nesting depth of for loops,
|
* Since this algorithm has exponential runtime in the nesting depth of for loops,
|
||||||
* a shortcut is taken at a certain nesting level: We only use the zero- and
|
* a shortcut is taken at a certain nesting level: We only use the zero- and
|
||||||
@ -93,14 +100,13 @@ struct Dialect;
|
|||||||
* For switch statements that have a "default"-case, there is no control-flow
|
* For switch statements that have a "default"-case, there is no control-flow
|
||||||
* part that skips the switch.
|
* part that skips the switch.
|
||||||
*
|
*
|
||||||
* At ``leave`` statements, all return variables are set to "used".
|
* At ``leave`` statements, all return variables are set to "used" and the set of active statements
|
||||||
|
* is cleared.
|
||||||
*
|
*
|
||||||
* When a variable goes out of scope, all statements still in the "undecided"
|
* If a function or builtin is called that does not continue, the set of active statements is
|
||||||
* state are changed to "unused", unless the variable is the return
|
* cleared for all variables.
|
||||||
* parameter of a function - there, the state changes to "used".
|
|
||||||
*
|
|
||||||
* In the second traversal, all assignments that are in the "unused" state are removed.
|
|
||||||
*
|
*
|
||||||
|
* In the second traversal, all assignments that are not marked as "used" are removed.
|
||||||
*
|
*
|
||||||
* This step is usually run right after the SSA transform to complete
|
* This step is usually run right after the SSA transform to complete
|
||||||
* the generation of the pseudo-SSA.
|
* the generation of the pseudo-SSA.
|
||||||
@ -113,12 +119,18 @@ public:
|
|||||||
static constexpr char const* name{"UnusedAssignEliminator"};
|
static constexpr char const* name{"UnusedAssignEliminator"};
|
||||||
static void run(OptimiserStepContext&, Block& _ast);
|
static void run(OptimiserStepContext&, Block& _ast);
|
||||||
|
|
||||||
explicit UnusedAssignEliminator(Dialect const& _dialect): UnusedStoreBase(_dialect) {}
|
explicit UnusedAssignEliminator(
|
||||||
|
Dialect const& _dialect,
|
||||||
|
std::map<YulString, ControlFlowSideEffects> _controlFlowSideEffects
|
||||||
|
):
|
||||||
|
UnusedStoreBase(_dialect),
|
||||||
|
m_controlFlowSideEffects(_controlFlowSideEffects)
|
||||||
|
{}
|
||||||
|
|
||||||
void operator()(Identifier const& _identifier) override;
|
void operator()(Identifier const& _identifier) override;
|
||||||
void operator()(VariableDeclaration const& _variableDeclaration) override;
|
|
||||||
void operator()(Assignment const& _assignment) override;
|
void operator()(Assignment const& _assignment) override;
|
||||||
void operator()(FunctionDefinition const&) override;
|
void operator()(FunctionDefinition const&) override;
|
||||||
|
void operator()(FunctionCall const& _functionCall) override;
|
||||||
void operator()(Leave const&) override;
|
void operator()(Leave const&) override;
|
||||||
void operator()(Block const& _block) override;
|
void operator()(Block const& _block) override;
|
||||||
|
|
||||||
@ -126,18 +138,13 @@ public:
|
|||||||
void visit(Statement const& _statement) override;
|
void visit(Statement const& _statement) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void shortcutNestedLoop(TrackedStores const& _beforeLoop) override;
|
void shortcutNestedLoop(ActiveStores const& _beforeLoop) override;
|
||||||
void finalizeFunctionDefinition(FunctionDefinition const& _functionDefinition) override;
|
void finalizeFunctionDefinition(FunctionDefinition const& _functionDefinition) override;
|
||||||
|
|
||||||
void changeUndecidedTo(YulString _variable, State _newState);
|
void markUsed(YulString _variable);
|
||||||
/// Called when a variable goes out of scope. Sets the state of all still undecided
|
|
||||||
/// assignments to the final state. In this case, this also applies to pending
|
|
||||||
/// break and continue TrackedStores.
|
|
||||||
void finalize(YulString _variable, State _finalState);
|
|
||||||
|
|
||||||
|
|
||||||
std::set<YulString> m_declaredVariables;
|
|
||||||
std::set<YulString> m_returnVariables;
|
std::set<YulString> m_returnVariables;
|
||||||
|
std::map<YulString, ControlFlowSideEffects> m_controlFlowSideEffects;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -37,47 +37,50 @@ void UnusedStoreBase::operator()(If const& _if)
|
|||||||
{
|
{
|
||||||
visit(*_if.condition);
|
visit(*_if.condition);
|
||||||
|
|
||||||
TrackedStores skipBranch{m_stores};
|
ActiveStores skipBranch{m_activeStores};
|
||||||
(*this)(_if.body);
|
(*this)(_if.body);
|
||||||
|
|
||||||
merge(m_stores, std::move(skipBranch));
|
merge(m_activeStores, std::move(skipBranch));
|
||||||
}
|
}
|
||||||
|
|
||||||
void UnusedStoreBase::operator()(Switch const& _switch)
|
void UnusedStoreBase::operator()(Switch const& _switch)
|
||||||
{
|
{
|
||||||
visit(*_switch.expression);
|
visit(*_switch.expression);
|
||||||
|
|
||||||
TrackedStores const preState{m_stores};
|
ActiveStores const preState{m_activeStores};
|
||||||
|
|
||||||
bool hasDefault = false;
|
bool hasDefault = false;
|
||||||
vector<TrackedStores> branches;
|
vector<ActiveStores> branches;
|
||||||
for (auto const& c: _switch.cases)
|
for (auto const& c: _switch.cases)
|
||||||
{
|
{
|
||||||
if (!c.value)
|
if (!c.value)
|
||||||
hasDefault = true;
|
hasDefault = true;
|
||||||
(*this)(c.body);
|
(*this)(c.body);
|
||||||
branches.emplace_back(std::move(m_stores));
|
branches.emplace_back(std::move(m_activeStores));
|
||||||
m_stores = preState;
|
m_activeStores = preState;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasDefault)
|
if (hasDefault)
|
||||||
{
|
{
|
||||||
m_stores = std::move(branches.back());
|
m_activeStores = std::move(branches.back());
|
||||||
branches.pop_back();
|
branches.pop_back();
|
||||||
}
|
}
|
||||||
for (auto& branch: branches)
|
for (auto& branch: branches)
|
||||||
merge(m_stores, std::move(branch));
|
merge(m_activeStores, std::move(branch));
|
||||||
}
|
}
|
||||||
|
|
||||||
void UnusedStoreBase::operator()(FunctionDefinition const& _functionDefinition)
|
void UnusedStoreBase::operator()(FunctionDefinition const& _functionDefinition)
|
||||||
{
|
{
|
||||||
ScopedSaveAndRestore outerAssignments(m_stores, {});
|
ScopedSaveAndRestore allStores(m_allStores, {});
|
||||||
|
ScopedSaveAndRestore usedStores(m_usedStores, {});
|
||||||
|
ScopedSaveAndRestore outerAssignments(m_activeStores, {});
|
||||||
ScopedSaveAndRestore forLoopInfo(m_forLoopInfo, {});
|
ScopedSaveAndRestore forLoopInfo(m_forLoopInfo, {});
|
||||||
ScopedSaveAndRestore forLoopNestingDepth(m_forLoopNestingDepth, 0);
|
ScopedSaveAndRestore forLoopNestingDepth(m_forLoopNestingDepth, 0);
|
||||||
|
|
||||||
(*this)(_functionDefinition.body);
|
(*this)(_functionDefinition.body);
|
||||||
|
|
||||||
finalizeFunctionDefinition(_functionDefinition);
|
finalizeFunctionDefinition(_functionDefinition);
|
||||||
|
m_storesToRemove += m_allStores - m_usedStores;
|
||||||
}
|
}
|
||||||
|
|
||||||
void UnusedStoreBase::operator()(ForLoop const& _forLoop)
|
void UnusedStoreBase::operator()(ForLoop const& _forLoop)
|
||||||
@ -94,10 +97,10 @@ void UnusedStoreBase::operator()(ForLoop const& _forLoop)
|
|||||||
|
|
||||||
visit(*_forLoop.condition);
|
visit(*_forLoop.condition);
|
||||||
|
|
||||||
TrackedStores zeroRuns{m_stores};
|
ActiveStores zeroRuns{m_activeStores};
|
||||||
|
|
||||||
(*this)(_forLoop.body);
|
(*this)(_forLoop.body);
|
||||||
merge(m_stores, std::move(m_forLoopInfo.pendingContinueStmts));
|
merge(m_activeStores, std::move(m_forLoopInfo.pendingContinueStmts));
|
||||||
m_forLoopInfo.pendingContinueStmts = {};
|
m_forLoopInfo.pendingContinueStmts = {};
|
||||||
(*this)(_forLoop.post);
|
(*this)(_forLoop.post);
|
||||||
|
|
||||||
@ -106,54 +109,54 @@ void UnusedStoreBase::operator()(ForLoop const& _forLoop)
|
|||||||
if (m_forLoopNestingDepth < 6)
|
if (m_forLoopNestingDepth < 6)
|
||||||
{
|
{
|
||||||
// Do the second run only for small nesting depths to avoid horrible runtime.
|
// Do the second run only for small nesting depths to avoid horrible runtime.
|
||||||
TrackedStores oneRun{m_stores};
|
ActiveStores oneRun{m_activeStores};
|
||||||
|
|
||||||
(*this)(_forLoop.body);
|
(*this)(_forLoop.body);
|
||||||
|
|
||||||
merge(m_stores, std::move(m_forLoopInfo.pendingContinueStmts));
|
merge(m_activeStores, std::move(m_forLoopInfo.pendingContinueStmts));
|
||||||
m_forLoopInfo.pendingContinueStmts.clear();
|
m_forLoopInfo.pendingContinueStmts.clear();
|
||||||
(*this)(_forLoop.post);
|
(*this)(_forLoop.post);
|
||||||
|
|
||||||
visit(*_forLoop.condition);
|
visit(*_forLoop.condition);
|
||||||
// Order of merging does not matter because "max" is commutative and associative.
|
// Order of merging does not matter because "max" is commutative and associative.
|
||||||
merge(m_stores, std::move(oneRun));
|
merge(m_activeStores, std::move(oneRun));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
// Shortcut to avoid horrible runtime.
|
// Shortcut to avoid horrible runtime.
|
||||||
shortcutNestedLoop(zeroRuns);
|
shortcutNestedLoop(zeroRuns);
|
||||||
|
|
||||||
// Order of merging does not matter because "max" is commutative and associative.
|
// Order of merging does not matter because "max" is commutative and associative.
|
||||||
merge(m_stores, std::move(zeroRuns));
|
merge(m_activeStores, std::move(zeroRuns));
|
||||||
merge(m_stores, std::move(m_forLoopInfo.pendingBreakStmts));
|
merge(m_activeStores, std::move(m_forLoopInfo.pendingBreakStmts));
|
||||||
m_forLoopInfo.pendingBreakStmts.clear();
|
m_forLoopInfo.pendingBreakStmts.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
void UnusedStoreBase::operator()(Break const&)
|
void UnusedStoreBase::operator()(Break const&)
|
||||||
{
|
{
|
||||||
m_forLoopInfo.pendingBreakStmts.emplace_back(std::move(m_stores));
|
m_forLoopInfo.pendingBreakStmts.emplace_back(std::move(m_activeStores));
|
||||||
m_stores.clear();
|
m_activeStores.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
void UnusedStoreBase::operator()(Continue const&)
|
void UnusedStoreBase::operator()(Continue const&)
|
||||||
{
|
{
|
||||||
m_forLoopInfo.pendingContinueStmts.emplace_back(std::move(m_stores));
|
m_forLoopInfo.pendingContinueStmts.emplace_back(std::move(m_activeStores));
|
||||||
m_stores.clear();
|
m_activeStores.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
void UnusedStoreBase::merge(TrackedStores& _target, TrackedStores&& _other)
|
void UnusedStoreBase::merge(ActiveStores& _target, ActiveStores&& _other)
|
||||||
{
|
{
|
||||||
util::joinMap(_target, std::move(_other), [](
|
util::joinMap(_target, std::move(_other), [](
|
||||||
map<Statement const*, State>& _assignmentHere,
|
set<Statement const*>& _storesHere,
|
||||||
map<Statement const*, State>&& _assignmentThere
|
set<Statement const*>&& _storesThere
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
return util::joinMap(_assignmentHere, std::move(_assignmentThere), State::join);
|
_storesHere += _storesThere;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void UnusedStoreBase::merge(TrackedStores& _target, vector<TrackedStores>&& _source)
|
void UnusedStoreBase::merge(ActiveStores& _target, vector<ActiveStores>&& _source)
|
||||||
{
|
{
|
||||||
for (TrackedStores& ts: _source)
|
for (ActiveStores& ts: _source)
|
||||||
merge(_target, std::move(ts));
|
merge(_target, std::move(ts));
|
||||||
_source.clear();
|
_source.clear();
|
||||||
}
|
}
|
||||||
|
@ -38,8 +38,11 @@ struct Dialect;
|
|||||||
*
|
*
|
||||||
* The class tracks the state of abstract "stores" (assignments or mstore/sstore
|
* The class tracks the state of abstract "stores" (assignments or mstore/sstore
|
||||||
* statements) across the control-flow. It is the job of the derived class to create
|
* statements) across the control-flow. It is the job of the derived class to create
|
||||||
* the stores and track references, but the base class adjusts their "used state" at
|
* the stores and track references, but the base class manages control-flow splits and joins.
|
||||||
* control-flow splits and joins.
|
*
|
||||||
|
* In general, active stores are those where it has not yet been determined if they are used
|
||||||
|
* or not. Those are split and joined at control-flow forks. Once a store has been deemed
|
||||||
|
* used, it is removed from the active set and marked as used and this will never change.
|
||||||
*
|
*
|
||||||
* Prerequisite: Disambiguator, ForLoopInitRewriter.
|
* Prerequisite: Disambiguator, ForLoopInitRewriter.
|
||||||
*/
|
*/
|
||||||
@ -57,28 +60,12 @@ public:
|
|||||||
void operator()(Continue const&) override;
|
void operator()(Continue const&) override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
class State
|
using ActiveStores = std::map<YulString, std::set<Statement const*>>;
|
||||||
{
|
|
||||||
public:
|
|
||||||
enum Value { Unused, Undecided, Used };
|
|
||||||
State(Value _value = Undecided): m_value(_value) {}
|
|
||||||
inline bool operator==(State _other) const { return m_value == _other.m_value; }
|
|
||||||
inline bool operator!=(State _other) const { return !operator==(_other); }
|
|
||||||
static inline void join(State& _a, State const& _b)
|
|
||||||
{
|
|
||||||
// Using "max" works here because of the order of the values in the enum.
|
|
||||||
_a.m_value = Value(std::max(int(_a.m_value), int(_b.m_value)));
|
|
||||||
}
|
|
||||||
private:
|
|
||||||
Value m_value = Undecided;
|
|
||||||
};
|
|
||||||
|
|
||||||
using TrackedStores = std::map<YulString, std::map<Statement const*, State>>;
|
|
||||||
|
|
||||||
/// This function is called for a loop that is nested too deep to avoid
|
/// This function is called for a loop that is nested too deep to avoid
|
||||||
/// horrible runtime and should just resolve the situation in a pragmatic
|
/// horrible runtime and should just resolve the situation in a pragmatic
|
||||||
/// and correct manner.
|
/// and correct manner.
|
||||||
virtual void shortcutNestedLoop(TrackedStores const& _beforeLoop) = 0;
|
virtual void shortcutNestedLoop(ActiveStores const& _beforeLoop) = 0;
|
||||||
|
|
||||||
/// This function is called right before the scoped restore of the function definition.
|
/// This function is called right before the scoped restore of the function definition.
|
||||||
virtual void finalizeFunctionDefinition(FunctionDefinition const& /*_functionDefinition*/) {}
|
virtual void finalizeFunctionDefinition(FunctionDefinition const& /*_functionDefinition*/) {}
|
||||||
@ -86,20 +73,26 @@ protected:
|
|||||||
/// Joins the assignment mapping of @a _source into @a _target according to the rules laid out
|
/// Joins the assignment mapping of @a _source into @a _target according to the rules laid out
|
||||||
/// above.
|
/// above.
|
||||||
/// Will destroy @a _source.
|
/// Will destroy @a _source.
|
||||||
static void merge(TrackedStores& _target, TrackedStores&& _source);
|
static void merge(ActiveStores& _target, ActiveStores&& _source);
|
||||||
static void merge(TrackedStores& _target, std::vector<TrackedStores>&& _source);
|
static void merge(ActiveStores& _target, std::vector<ActiveStores>&& _source);
|
||||||
|
|
||||||
Dialect const& m_dialect;
|
Dialect const& m_dialect;
|
||||||
std::set<Statement const*> m_pendingRemovals;
|
/// Set of all stores encountered during the traversal (in the current function).
|
||||||
TrackedStores m_stores;
|
std::set<Statement const*> m_allStores;
|
||||||
|
/// Set of stores that are marked as being used (in the current function).
|
||||||
|
std::set<Statement const*> m_usedStores;
|
||||||
|
/// List of stores that can be removed (globally).
|
||||||
|
std::vector<Statement const*> m_storesToRemove;
|
||||||
|
/// Active (undecided) stores in the current branch.
|
||||||
|
ActiveStores m_activeStores;
|
||||||
|
|
||||||
/// Working data for traversing for-loops.
|
/// Working data for traversing for-loops.
|
||||||
struct ForLoopInfo
|
struct ForLoopInfo
|
||||||
{
|
{
|
||||||
/// Tracked assignment states for each break statement.
|
/// Tracked assignment states for each break statement.
|
||||||
std::vector<TrackedStores> pendingBreakStmts;
|
std::vector<ActiveStores> pendingBreakStmts;
|
||||||
/// Tracked assignment states for each continue statement.
|
/// Tracked assignment states for each continue statement.
|
||||||
std::vector<TrackedStores> pendingContinueStmts;
|
std::vector<ActiveStores> pendingContinueStmts;
|
||||||
};
|
};
|
||||||
ForLoopInfo m_forLoopInfo;
|
ForLoopInfo m_forLoopInfo;
|
||||||
size_t m_forLoopNestingDepth = 0;
|
size_t m_forLoopNestingDepth = 0;
|
||||||
|
@ -78,17 +78,17 @@ void UnusedStoreEliminator::run(OptimiserStepContext& _context, Block& _ast)
|
|||||||
ignoreMemory
|
ignoreMemory
|
||||||
};
|
};
|
||||||
rse(_ast);
|
rse(_ast);
|
||||||
if (
|
|
||||||
auto evmDialect = dynamic_cast<EVMDialect const*>(&_context.dialect);
|
|
||||||
evmDialect && evmDialect->providesObjectAccess()
|
|
||||||
)
|
|
||||||
rse.changeUndecidedTo(State::Unused, Location::Memory);
|
|
||||||
else
|
|
||||||
rse.changeUndecidedTo(State::Used, Location::Memory);
|
|
||||||
rse.changeUndecidedTo(State::Used, Location::Storage);
|
|
||||||
rse.scheduleUnusedForDeletion();
|
|
||||||
|
|
||||||
StatementRemover remover(rse.m_pendingRemovals);
|
auto evmDialect = dynamic_cast<EVMDialect const*>(&_context.dialect);
|
||||||
|
if (evmDialect && evmDialect->providesObjectAccess())
|
||||||
|
rse.clearActive(Location::Memory);
|
||||||
|
else
|
||||||
|
rse.markActiveAsUsed(Location::Memory);
|
||||||
|
rse.markActiveAsUsed(Location::Storage);
|
||||||
|
rse.m_storesToRemove += rse.m_allStores - rse.m_usedStores;
|
||||||
|
|
||||||
|
set<Statement const*> toRemove{rse.m_storesToRemove.begin(), rse.m_storesToRemove.end()};
|
||||||
|
StatementRemover remover{toRemove};
|
||||||
remover(_ast);
|
remover(_ast);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -121,12 +121,12 @@ void UnusedStoreEliminator::operator()(FunctionCall const& _functionCall)
|
|||||||
sideEffects = m_controlFlowSideEffects.at(_functionCall.functionName.name);
|
sideEffects = m_controlFlowSideEffects.at(_functionCall.functionName.name);
|
||||||
|
|
||||||
if (sideEffects.canTerminate)
|
if (sideEffects.canTerminate)
|
||||||
changeUndecidedTo(State::Used, Location::Storage);
|
markActiveAsUsed(Location::Storage);
|
||||||
if (!sideEffects.canContinue)
|
if (!sideEffects.canContinue)
|
||||||
{
|
{
|
||||||
changeUndecidedTo(State::Unused, Location::Memory);
|
clearActive(Location::Memory);
|
||||||
if (!sideEffects.canTerminate)
|
if (!sideEffects.canTerminate)
|
||||||
changeUndecidedTo(State::Unused, Location::Storage);
|
clearActive(Location::Storage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -139,7 +139,7 @@ void UnusedStoreEliminator::operator()(FunctionDefinition const& _functionDefini
|
|||||||
|
|
||||||
void UnusedStoreEliminator::operator()(Leave const&)
|
void UnusedStoreEliminator::operator()(Leave const&)
|
||||||
{
|
{
|
||||||
changeUndecidedTo(State::Used);
|
markActiveAsUsed();
|
||||||
}
|
}
|
||||||
|
|
||||||
void UnusedStoreEliminator::visit(Statement const& _statement)
|
void UnusedStoreEliminator::visit(Statement const& _statement)
|
||||||
@ -183,10 +183,14 @@ void UnusedStoreEliminator::visit(Statement const& _statement)
|
|||||||
yulAssert(isCandidateForRemoval == (isStorageWrite || (!m_ignoreMemory && isMemoryWrite)));
|
yulAssert(isCandidateForRemoval == (isStorageWrite || (!m_ignoreMemory && isMemoryWrite)));
|
||||||
if (isCandidateForRemoval)
|
if (isCandidateForRemoval)
|
||||||
{
|
{
|
||||||
State initialState = State::Undecided;
|
|
||||||
if (*instruction == Instruction::RETURNDATACOPY)
|
if (*instruction == Instruction::RETURNDATACOPY)
|
||||||
{
|
{
|
||||||
initialState = State::Used;
|
// Out-of-bounds access to the returndata buffer results in a revert,
|
||||||
|
// so we are careful not to remove a potentially reverting call to a builtin.
|
||||||
|
// The only way the Solidity compiler uses `returndatacopy` is
|
||||||
|
// `returndatacopy(X, 0, returndatasize())`, so we only allow to remove this pattern
|
||||||
|
// (which is guaranteed to never cause an out-of-bounds revert).
|
||||||
|
bool allowReturndatacopyToBeRemoved = false;
|
||||||
auto startOffset = identifierNameIfSSA(funCall->arguments.at(1));
|
auto startOffset = identifierNameIfSSA(funCall->arguments.at(1));
|
||||||
auto length = identifierNameIfSSA(funCall->arguments.at(2));
|
auto length = identifierNameIfSSA(funCall->arguments.at(2));
|
||||||
if (length && startOffset)
|
if (length && startOffset)
|
||||||
@ -197,22 +201,22 @@ void UnusedStoreEliminator::visit(Statement const& _statement)
|
|||||||
lengthCall &&
|
lengthCall &&
|
||||||
toEVMInstruction(m_dialect, lengthCall->functionName.name) == Instruction::RETURNDATASIZE
|
toEVMInstruction(m_dialect, lengthCall->functionName.name) == Instruction::RETURNDATASIZE
|
||||||
)
|
)
|
||||||
initialState = State::Undecided;
|
allowReturndatacopyToBeRemoved = true;
|
||||||
}
|
}
|
||||||
|
if (!allowReturndatacopyToBeRemoved)
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
m_stores[YulString{}].insert({&_statement, initialState});
|
m_allStores.insert(&_statement);
|
||||||
vector<Operation> operations = operationsFromFunctionCall(*funCall);
|
vector<Operation> operations = operationsFromFunctionCall(*funCall);
|
||||||
yulAssert(operations.size() == 1, "");
|
yulAssert(operations.size() == 1, "");
|
||||||
|
if (operations.front().location == Location::Storage)
|
||||||
|
activeStorageStores().insert(&_statement);
|
||||||
|
else
|
||||||
|
activeMemoryStores().insert(&_statement);
|
||||||
m_storeOperations[&_statement] = std::move(operations.front());
|
m_storeOperations[&_statement] = std::move(operations.front());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void UnusedStoreEliminator::finalizeFunctionDefinition(FunctionDefinition const&)
|
|
||||||
{
|
|
||||||
changeUndecidedTo(State::Used);
|
|
||||||
scheduleUnusedForDeletion();
|
|
||||||
}
|
|
||||||
|
|
||||||
vector<UnusedStoreEliminator::Operation> UnusedStoreEliminator::operationsFromFunctionCall(
|
vector<UnusedStoreEliminator::Operation> UnusedStoreEliminator::operationsFromFunctionCall(
|
||||||
FunctionCall const& _functionCall
|
FunctionCall const& _functionCall
|
||||||
) const
|
) const
|
||||||
@ -265,14 +269,27 @@ vector<UnusedStoreEliminator::Operation> UnusedStoreEliminator::operationsFromFu
|
|||||||
|
|
||||||
void UnusedStoreEliminator::applyOperation(UnusedStoreEliminator::Operation const& _operation)
|
void UnusedStoreEliminator::applyOperation(UnusedStoreEliminator::Operation const& _operation)
|
||||||
{
|
{
|
||||||
for (auto& [statement, state]: m_stores[YulString{}])
|
set<Statement const*>& active =
|
||||||
if (state == State::Undecided)
|
_operation.location == Location::Storage ?
|
||||||
|
activeStorageStores() :
|
||||||
|
activeMemoryStores();
|
||||||
|
|
||||||
|
|
||||||
|
for (auto it = active.begin(); it != active.end();)
|
||||||
{
|
{
|
||||||
|
Statement const* statement = *it;
|
||||||
Operation const& storeOperation = m_storeOperations.at(statement);
|
Operation const& storeOperation = m_storeOperations.at(statement);
|
||||||
if (_operation.effect == Effect::Read && !knownUnrelated(storeOperation, _operation))
|
if (_operation.effect == Effect::Read && !knownUnrelated(storeOperation, _operation))
|
||||||
state = State::Used;
|
{
|
||||||
|
// This store is read from, mark it as used and remove it from the active set.
|
||||||
|
m_usedStores.insert(statement);
|
||||||
|
it = active.erase(it);
|
||||||
|
}
|
||||||
else if (_operation.effect == Effect::Write && knownCovered(storeOperation, _operation))
|
else if (_operation.effect == Effect::Write && knownCovered(storeOperation, _operation))
|
||||||
state = State::Unused;
|
// This store is overwritten before being read, remove it from the active set.
|
||||||
|
it = active.erase(it);
|
||||||
|
else
|
||||||
|
++it;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -390,16 +407,27 @@ bool UnusedStoreEliminator::knownCovered(
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void UnusedStoreEliminator::changeUndecidedTo(
|
void UnusedStoreEliminator::markActiveAsUsed(
|
||||||
State _newState,
|
optional<UnusedStoreEliminator::Location> _onlyLocation
|
||||||
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;
|
{
|
||||||
|
if (_onlyLocation == nullopt || _onlyLocation == Location::Memory)
|
||||||
|
for (Statement const* statement: activeMemoryStores())
|
||||||
|
m_usedStores.insert(statement);
|
||||||
|
if (_onlyLocation == nullopt || _onlyLocation == Location::Storage)
|
||||||
|
for (Statement const* statement: activeStorageStores())
|
||||||
|
m_usedStores.insert(statement);
|
||||||
|
clearActive(_onlyLocation);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UnusedStoreEliminator::clearActive(
|
||||||
|
optional<UnusedStoreEliminator::Location> _onlyLocation
|
||||||
|
)
|
||||||
|
{
|
||||||
|
if (_onlyLocation == nullopt || _onlyLocation == Location::Memory)
|
||||||
|
activeMemoryStores() = {};
|
||||||
|
if (_onlyLocation == nullopt || _onlyLocation == Location::Storage)
|
||||||
|
activeStorageStores() = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
optional<YulString> UnusedStoreEliminator::identifierNameIfSSA(Expression const& _expression) const
|
optional<YulString> UnusedStoreEliminator::identifierNameIfSSA(Expression const& _expression) const
|
||||||
@ -409,10 +437,3 @@ optional<YulString> UnusedStoreEliminator::identifierNameIfSSA(Expression const&
|
|||||||
return {identifier->name};
|
return {identifier->name};
|
||||||
return nullopt;
|
return nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
void UnusedStoreEliminator::scheduleUnusedForDeletion()
|
|
||||||
{
|
|
||||||
for (auto const& [statement, state]: m_stores[YulString{}])
|
|
||||||
if (state == State::Unused)
|
|
||||||
m_pendingRemovals.insert(statement);
|
|
||||||
}
|
|
||||||
|
@ -50,8 +50,7 @@ struct AssignedValue;
|
|||||||
* to sstore, as we don't know whether the memory location will be read once we leave the function's scope,
|
* to sstore, as we don't know whether the memory location will be read once we leave the function's scope,
|
||||||
* so the statement will be removed only if all code code paths lead to a memory overwrite.
|
* so the statement will be removed only if all code code paths lead to a memory overwrite.
|
||||||
*
|
*
|
||||||
* The m_store member of UnusedStoreBase is only used with the empty yul string
|
* The m_store member of UnusedStoreBase uses the key "m" for memory and "s" for storage stores.
|
||||||
* as key in the first dimension.
|
|
||||||
*
|
*
|
||||||
* Best run in SSA form.
|
* Best run in SSA form.
|
||||||
*
|
*
|
||||||
@ -93,20 +92,26 @@ public:
|
|||||||
};
|
};
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void shortcutNestedLoop(TrackedStores const&) override
|
std::set<Statement const*>& activeMemoryStores() { return m_activeStores["m"_yulstring]; }
|
||||||
|
std::set<Statement const*>& activeStorageStores() { return m_activeStores["s"_yulstring]; }
|
||||||
|
|
||||||
|
void shortcutNestedLoop(ActiveStores const&) override
|
||||||
{
|
{
|
||||||
// We might only need to do this for newly introduced stores in the loop.
|
// We might only need to do this for newly introduced stores in the loop.
|
||||||
changeUndecidedTo(State::Used);
|
markActiveAsUsed();
|
||||||
|
}
|
||||||
|
void finalizeFunctionDefinition(FunctionDefinition const&) override
|
||||||
|
{
|
||||||
|
markActiveAsUsed();
|
||||||
}
|
}
|
||||||
void finalizeFunctionDefinition(FunctionDefinition const&) override;
|
|
||||||
|
|
||||||
std::vector<Operation> operationsFromFunctionCall(FunctionCall const& _functionCall) const;
|
std::vector<Operation> operationsFromFunctionCall(FunctionCall const& _functionCall) const;
|
||||||
void applyOperation(Operation const& _operation);
|
void applyOperation(Operation const& _operation);
|
||||||
bool knownUnrelated(Operation const& _op1, Operation const& _op2) const;
|
bool knownUnrelated(Operation const& _op1, Operation const& _op2) const;
|
||||||
bool knownCovered(Operation const& _covered, Operation const& _covering) const;
|
bool knownCovered(Operation const& _covered, Operation const& _covering) const;
|
||||||
|
|
||||||
void changeUndecidedTo(State _newState, std::optional<Location> _onlyLocation = std::nullopt);
|
void markActiveAsUsed(std::optional<Location> _onlyLocation = std::nullopt);
|
||||||
void scheduleUnusedForDeletion();
|
void clearActive(std::optional<Location> _onlyLocation = std::nullopt);
|
||||||
|
|
||||||
std::optional<YulString> identifierNameIfSSA(Expression const& _expression) const;
|
std::optional<YulString> identifierNameIfSSA(Expression const& _expression) const;
|
||||||
|
|
||||||
|
@ -176,7 +176,7 @@ contract DepositContract is IDepositContract, ERC165 {
|
|||||||
}
|
}
|
||||||
// ----
|
// ----
|
||||||
// constructor()
|
// constructor()
|
||||||
// gas irOptimized: 1430741
|
// gas irOptimized: 1419712
|
||||||
// gas legacy: 2427905
|
// gas legacy: 2427905
|
||||||
// gas legacyOptimized: 1773081
|
// gas legacyOptimized: 1773081
|
||||||
// supportsInterface(bytes4): 0x0 -> 0
|
// supportsInterface(bytes4): 0x0 -> 0
|
||||||
|
@ -0,0 +1,33 @@
|
|||||||
|
{
|
||||||
|
function g() {
|
||||||
|
if calldataload(10) { revert(0, 0) }
|
||||||
|
}
|
||||||
|
function f() {
|
||||||
|
let a := calldataload(0)
|
||||||
|
if calldataload(1) {
|
||||||
|
// this can NOT be removed
|
||||||
|
a := 2
|
||||||
|
g()
|
||||||
|
}
|
||||||
|
sstore(0, a)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ----
|
||||||
|
// step: unusedAssignEliminator
|
||||||
|
//
|
||||||
|
// {
|
||||||
|
// function g()
|
||||||
|
// {
|
||||||
|
// if calldataload(10) { revert(0, 0) }
|
||||||
|
// }
|
||||||
|
// function f()
|
||||||
|
// {
|
||||||
|
// let a := calldataload(0)
|
||||||
|
// if calldataload(1)
|
||||||
|
// {
|
||||||
|
// a := 2
|
||||||
|
// g()
|
||||||
|
// }
|
||||||
|
// sstore(0, a)
|
||||||
|
// }
|
||||||
|
// }
|
@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
function f() {
|
||||||
|
let a := calldataload(0)
|
||||||
|
if calldataload(1) {
|
||||||
|
// this can be removed
|
||||||
|
a := 2
|
||||||
|
leave
|
||||||
|
}
|
||||||
|
sstore(0, a)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ----
|
||||||
|
// step: unusedAssignEliminator
|
||||||
|
//
|
||||||
|
// {
|
||||||
|
// function f()
|
||||||
|
// {
|
||||||
|
// let a := calldataload(0)
|
||||||
|
// if calldataload(1) { leave }
|
||||||
|
// sstore(0, a)
|
||||||
|
// }
|
||||||
|
// }
|
@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
let a := calldataload(0)
|
||||||
|
if calldataload(1) {
|
||||||
|
// this can be removed
|
||||||
|
a := 2
|
||||||
|
revert(0, 0)
|
||||||
|
}
|
||||||
|
sstore(0, a)
|
||||||
|
}
|
||||||
|
// ----
|
||||||
|
// step: unusedAssignEliminator
|
||||||
|
//
|
||||||
|
// {
|
||||||
|
// let a := calldataload(0)
|
||||||
|
// if calldataload(1) { revert(0, 0) }
|
||||||
|
// sstore(0, a)
|
||||||
|
// }
|
@ -46,10 +46,7 @@
|
|||||||
// for { } 1 { }
|
// for { } 1 { }
|
||||||
// {
|
// {
|
||||||
// for { } 1 { a := 10 }
|
// for { } 1 { a := 10 }
|
||||||
// {
|
// { b := 11 }
|
||||||
// b := 12
|
|
||||||
// b := 11
|
|
||||||
// }
|
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
@ -0,0 +1,35 @@
|
|||||||
|
{
|
||||||
|
function g() -> x {
|
||||||
|
x := 7
|
||||||
|
if calldataload(0) {
|
||||||
|
x := 3
|
||||||
|
reverting()
|
||||||
|
}
|
||||||
|
if calldataload(1) {
|
||||||
|
x := 3
|
||||||
|
leave
|
||||||
|
}
|
||||||
|
x := 2
|
||||||
|
reverting()
|
||||||
|
}
|
||||||
|
function reverting() { revert(0, 0) }
|
||||||
|
sstore(0, g())
|
||||||
|
}
|
||||||
|
// ----
|
||||||
|
// step: unusedAssignEliminator
|
||||||
|
//
|
||||||
|
// {
|
||||||
|
// function g() -> x
|
||||||
|
// {
|
||||||
|
// if calldataload(0) { reverting() }
|
||||||
|
// if calldataload(1)
|
||||||
|
// {
|
||||||
|
// x := 3
|
||||||
|
// leave
|
||||||
|
// }
|
||||||
|
// reverting()
|
||||||
|
// }
|
||||||
|
// function reverting()
|
||||||
|
// { revert(0, 0) }
|
||||||
|
// sstore(0, g())
|
||||||
|
// }
|
Loading…
Reference in New Issue
Block a user