mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Tune Rematerializer
This commit is contained in:
parent
d1a7ff0fbc
commit
25d3f27c11
@ -145,10 +145,14 @@ void DataFlowAnalyzer::operator()(FunctionDefinition& _fun)
|
||||
// Save all information. We might rather reinstantiate this class,
|
||||
// but this could be difficult if it is subclassed.
|
||||
map<YulString, Expression const*> value;
|
||||
map<YulString, size_t> variableLoopDepth;
|
||||
size_t loopDepth{0};
|
||||
InvertibleRelation<YulString> references;
|
||||
InvertibleMap<YulString, YulString> storage;
|
||||
InvertibleMap<YulString, YulString> memory;
|
||||
m_value.swap(value);
|
||||
swap(m_value, value);
|
||||
swap(m_variableLoopDepth, variableLoopDepth);
|
||||
swap(m_loopDepth, loopDepth);
|
||||
swap(m_references, references);
|
||||
swap(m_storage, storage);
|
||||
swap(m_memory, memory);
|
||||
@ -168,7 +172,9 @@ void DataFlowAnalyzer::operator()(FunctionDefinition& _fun)
|
||||
// statement.
|
||||
|
||||
popScope();
|
||||
m_value.swap(value);
|
||||
swap(m_value, value);
|
||||
swap(m_variableLoopDepth, variableLoopDepth);
|
||||
swap(m_loopDepth, loopDepth);
|
||||
swap(m_references, references);
|
||||
swap(m_storage, storage);
|
||||
swap(m_memory, memory);
|
||||
@ -180,6 +186,8 @@ void DataFlowAnalyzer::operator()(ForLoop& _for)
|
||||
// we would have to deal with more complicated scoping rules.
|
||||
assertThrow(_for.pre.statements.empty(), OptimizerException, "");
|
||||
|
||||
++m_loopDepth;
|
||||
|
||||
AssignmentsSinceContinue assignmentsSinceCont;
|
||||
assignmentsSinceCont(_for.body);
|
||||
|
||||
@ -202,6 +210,8 @@ void DataFlowAnalyzer::operator()(ForLoop& _for)
|
||||
clearKnowledgeIfInvalidated(*_for.condition);
|
||||
clearKnowledgeIfInvalidated(_for.post);
|
||||
clearKnowledgeIfInvalidated(_for.body);
|
||||
|
||||
--m_loopDepth;
|
||||
}
|
||||
|
||||
void DataFlowAnalyzer::operator()(Block& _block)
|
||||
@ -222,7 +232,7 @@ void DataFlowAnalyzer::handleAssignment(set<YulString> const& _variables, Expres
|
||||
movableChecker.visit(*_value);
|
||||
else
|
||||
for (auto const& var: _variables)
|
||||
m_value[var] = &m_zero;
|
||||
assignValue(var, &m_zero);
|
||||
|
||||
if (_value && _variables.size() == 1)
|
||||
{
|
||||
@ -230,7 +240,7 @@ void DataFlowAnalyzer::handleAssignment(set<YulString> const& _variables, Expres
|
||||
// Expression has to be movable and cannot contain a reference
|
||||
// to the variable that will be assigned to.
|
||||
if (movableChecker.movable() && !movableChecker.referencedVariables().count(name))
|
||||
m_value[name] = _value;
|
||||
assignValue(name, _value);
|
||||
}
|
||||
|
||||
auto const& referencedVariables = movableChecker.referencedVariables();
|
||||
@ -296,11 +306,20 @@ void DataFlowAnalyzer::clearValues(set<YulString> _variables)
|
||||
|
||||
// Clear the value and update the reference relation.
|
||||
for (auto const& name: _variables)
|
||||
{
|
||||
m_value.erase(name);
|
||||
m_variableLoopDepth.erase(name);
|
||||
}
|
||||
for (auto const& name: _variables)
|
||||
m_references.eraseKey(name);
|
||||
}
|
||||
|
||||
void DataFlowAnalyzer::assignValue(YulString _variable, Expression const* _value)
|
||||
{
|
||||
m_value[_variable] = _value;
|
||||
m_variableLoopDepth[_variable] = m_loopDepth;
|
||||
}
|
||||
|
||||
void DataFlowAnalyzer::clearKnowledgeIfInvalidated(Block const& _block)
|
||||
{
|
||||
SideEffectsCollector sideEffects(m_dialect, _block, &m_functionSideEffects);
|
||||
|
@ -110,6 +110,8 @@ protected:
|
||||
/// for example at points where control flow is merged.
|
||||
void clearValues(std::set<YulString> _names);
|
||||
|
||||
void assignValue(YulString _variable, Expression const* _value);
|
||||
|
||||
/// Clears knowledge about storage or memory if they may be modified inside the block.
|
||||
void clearKnowledgeIfInvalidated(Block const& _block);
|
||||
|
||||
@ -144,6 +146,8 @@ protected:
|
||||
|
||||
/// Current values of variables, always movable.
|
||||
std::map<YulString, Expression const*> m_value;
|
||||
/// The loop nesting depth of the definition of variables (those used in m_value).
|
||||
std::map<YulString, size_t> m_variableLoopDepth;
|
||||
/// m_references.forward[a].contains(b) <=> the current expression assigned to a references b
|
||||
/// m_references.backward[b].contains(a) <=> the current expression assigned to a references b
|
||||
InvertibleRelation<YulString> m_references;
|
||||
@ -153,6 +157,9 @@ protected:
|
||||
|
||||
KnowledgeBase m_knowledgeBase;
|
||||
|
||||
/// Current nesting depth of loops.
|
||||
size_t m_loopDepth{0};
|
||||
|
||||
struct Scope
|
||||
{
|
||||
explicit Scope(bool _isFunction): isFunction(_isFunction) {}
|
||||
|
@ -78,7 +78,12 @@ void Rematerialiser::visit(Expression& _e)
|
||||
auto const& value = *m_value.at(name);
|
||||
size_t refs = m_referenceCounts[name];
|
||||
size_t cost = CodeCost::codeCost(m_dialect, value);
|
||||
if (refs <= 1 || cost == 0 || (refs <= 5 && cost <= 1) || m_varsToAlwaysRematerialize.count(name))
|
||||
if (
|
||||
(refs <= 1 && m_variableLoopDepth.at(name) == m_loopDepth) ||
|
||||
cost == 0 ||
|
||||
(refs <= 5 && cost <= 1) ||
|
||||
m_varsToAlwaysRematerialize.count(name)
|
||||
)
|
||||
{
|
||||
assertThrow(m_referenceCounts[name] > 0, OptimizerException, "");
|
||||
for (auto const& ref: m_references.forward[name])
|
||||
|
@ -27,9 +27,10 @@ namespace solidity::yul
|
||||
{
|
||||
|
||||
/**
|
||||
* Optimisation stage that replaces variables by their most recently assigned expressions,
|
||||
* Optimisation stage that replaces variable references by those expressions
|
||||
* that are most recently assigned to the referenced variables,
|
||||
* but only if the expression is movable and one of the following holds:
|
||||
* - the variable is referenced exactly once
|
||||
* - the variable is referenced exactly once (and definition-to-reference does not cross a loop boundary)
|
||||
* - the value is extremely cheap ("cost" of zero like ``caller()``)
|
||||
* - the variable is referenced at most 5 times and the value is rather cheap
|
||||
* ("cost" of at most 1 like a constant up to 0xff)
|
||||
@ -68,6 +69,8 @@ protected:
|
||||
std::set<YulString> _varsToAlwaysRematerialize = {}
|
||||
);
|
||||
|
||||
using DataFlowAnalyzer::operator();
|
||||
|
||||
using ASTModifier::visit;
|
||||
void visit(Expression& _e) override;
|
||||
|
||||
|
@ -12,7 +12,8 @@
|
||||
// {
|
||||
// {
|
||||
// let y := mload(0x20)
|
||||
// for { } and(y, 8) { if y { revert(0, 0) } }
|
||||
// let _1 := iszero(and(y, 8))
|
||||
// for { } iszero(_1) { if y { revert(0, 0) } }
|
||||
// {
|
||||
// if y { continue }
|
||||
// sstore(1, 0)
|
||||
|
@ -21,8 +21,9 @@
|
||||
// {
|
||||
// let _1 := calldataload(0)
|
||||
// let sum := 0
|
||||
// let length := calldataload(_1)
|
||||
// let i := sum
|
||||
// for { } lt(i, calldataload(_1)) { i := add(i, 1) }
|
||||
// for { } lt(i, length) { i := add(i, 1) }
|
||||
// {
|
||||
// sum := add(sum, calldataload(add(add(_1, mul(i, 0x20)), 0x20)))
|
||||
// }
|
||||
|
@ -24,10 +24,12 @@
|
||||
// {
|
||||
// let _1 := calldataload(0)
|
||||
// let sum := 0
|
||||
// let length := calldataload(_1)
|
||||
// let i := sum
|
||||
// for { } lt(i, calldataload(_1)) { i := add(i, 1) }
|
||||
// let _2 := calldataload(7)
|
||||
// for { } lt(i, length) { i := add(i, 1) }
|
||||
// {
|
||||
// sum := add(sum, add(calldataload(add(add(_1, mul(i, 0x20)), 0x20)), calldataload(7)))
|
||||
// sum := add(sum, add(calldataload(add(add(_1, mul(i, 0x20)), 0x20)), _2))
|
||||
// }
|
||||
// sstore(0, sum)
|
||||
// }
|
||||
|
@ -12,10 +12,11 @@
|
||||
// ----
|
||||
// {
|
||||
// {
|
||||
// let _1 := iszero(caller())
|
||||
// for { }
|
||||
// 1
|
||||
// {
|
||||
// for { } caller() { }
|
||||
// for { } iszero(_1) { }
|
||||
// { }
|
||||
// mstore(192, 0)
|
||||
// }
|
||||
|
@ -0,0 +1,28 @@
|
||||
{
|
||||
// Cost of rematerializating x is 1
|
||||
let x := 0xff
|
||||
// Although x has low cost, it is not considered for
|
||||
// rematerialization because it is referenced more than 5 times
|
||||
for {} lt(x, 0x100) {}
|
||||
{
|
||||
let y := add(x, 1)
|
||||
let z := mul(x, 1)
|
||||
let a := div(x, 2)
|
||||
let b := mod(x, 3)
|
||||
let c := sdiv(x, 4)
|
||||
}
|
||||
}
|
||||
// ====
|
||||
// step: rematerialiser
|
||||
// ----
|
||||
// {
|
||||
// let x := 0xff
|
||||
// for { } lt(x, 0x100) { }
|
||||
// {
|
||||
// let y := add(x, 1)
|
||||
// let z := mul(x, 1)
|
||||
// let a := div(x, 2)
|
||||
// let b := mod(x, 3)
|
||||
// let c := sdiv(x, 4)
|
||||
// }
|
||||
// }
|
@ -0,0 +1,37 @@
|
||||
{
|
||||
// origin has zero cost and thus will be rematerialised,
|
||||
// calldataload(0) has low cost and will not be rematerialised
|
||||
let a := origin()
|
||||
let b := calldataload(0)
|
||||
let i := 0
|
||||
let z := calldataload(9)
|
||||
for {} lt(i, 10) {i := add(a, b)} {
|
||||
// This will be rematerialised, because it stays inside
|
||||
// the loop.
|
||||
let x := calldataload(1)
|
||||
mstore(9, x)
|
||||
// No, because again one loop further down.
|
||||
let y := calldataload(2)
|
||||
for {} y {} {
|
||||
// Again no.
|
||||
mstore(12, z)
|
||||
}
|
||||
}
|
||||
}
|
||||
// ====
|
||||
// step: rematerialiser
|
||||
// ----
|
||||
// {
|
||||
// let a := origin()
|
||||
// let b := calldataload(0)
|
||||
// let i := 0
|
||||
// let z := calldataload(9)
|
||||
// for { } lt(i, 10) { i := add(origin(), b) }
|
||||
// {
|
||||
// let x := calldataload(1)
|
||||
// mstore(9, calldataload(1))
|
||||
// let y := calldataload(2)
|
||||
// for { } y { }
|
||||
// { mstore(12, z) }
|
||||
// }
|
||||
// }
|
@ -0,0 +1,17 @@
|
||||
{
|
||||
// Cost of rematerializating x is 1
|
||||
let x := 0xff
|
||||
// Reference to x is not rematerialized because the reference is in a loop
|
||||
for {} lt(x, 0x100) {}
|
||||
{
|
||||
let y := add(x, 1)
|
||||
}
|
||||
}
|
||||
// ====
|
||||
// step: rematerialiser
|
||||
// ----
|
||||
// {
|
||||
// let x := 0xff
|
||||
// for { } lt(0xff, 0x100) { }
|
||||
// { let y := add(0xff, 1) }
|
||||
// }
|
@ -0,0 +1,26 @@
|
||||
{
|
||||
// Cost of rematerializating x is 1
|
||||
let x := 0xff
|
||||
// Although x has a low cost and fewer than 6 references,
|
||||
// its references in a loop are not rematerialized
|
||||
for {} lt(x, 0x100) {}
|
||||
{
|
||||
let y := add(x, 1)
|
||||
for {} lt(x, 0x200) {}
|
||||
{
|
||||
let z := mul(x, 2)
|
||||
}
|
||||
}
|
||||
}
|
||||
// ====
|
||||
// step: rematerialiser
|
||||
// ----
|
||||
// {
|
||||
// let x := 0xff
|
||||
// for { } lt(0xff, 0x100) { }
|
||||
// {
|
||||
// let y := add(0xff, 1)
|
||||
// for { } lt(0xff, 0x200) { }
|
||||
// { let z := mul(0xff, 2) }
|
||||
// }
|
||||
// }
|
Loading…
Reference in New Issue
Block a user