Browse Source

Storage access optimisation.

cl-refactor
chriseth 10 years ago
parent
commit
965f005a98
  1. 205
      libevmcore/CommonSubexpressionEliminator.cpp
  2. 67
      libevmcore/CommonSubexpressionEliminator.h
  3. 70
      libevmcore/ExpressionClasses.cpp
  4. 10
      libevmcore/ExpressionClasses.h
  5. 180
      test/SolidityOptimizer.cpp

205
libevmcore/CommonSubexpressionEliminator.cpp

@ -45,19 +45,24 @@ vector<AssemblyItem> CommonSubexpressionEliminator::getOptimizedItems()
// Debug info:
//stream(cout, initialStackContents, targetStackContents);
return CSECodeGenerator(m_expressionClasses).generateCode(initialStackContents, targetStackContents);
return CSECodeGenerator(m_expressionClasses, m_storageWrites).generateCode(
initialStackContents,
targetStackContents
);
}
ostream& CommonSubexpressionEliminator::stream(
ostream& _out,
map<int, ExpressionClasses::Id> _currentStack,
map<int, ExpressionClasses::Id> _initialStack,
map<int, ExpressionClasses::Id> _targetStack
) const
{
auto streamExpressionClass = [this](ostream& _out, ExpressionClasses::Id _id)
{
auto const& expr = m_expressionClasses.representative(_id);
_out << " " << _id << ": " << *expr.item;
_out << " " << dec << _id << ": " << *expr.item;
if (expr.sequenceNumber)
_out << "@" << dec << expr.sequenceNumber;
_out << "(";
for (ExpressionClasses::Id arg: expr.arguments)
_out << dec << arg << ",";
@ -66,18 +71,12 @@ ostream& CommonSubexpressionEliminator::stream(
_out << "Optimizer analysis:" << endl;
_out << "Final stack height: " << dec << m_stackHeight << endl;
_out << "Stack elements: " << endl;
for (auto const& it: m_stackElements)
{
_out << " " << dec << it.first << " = ";
streamExpressionClass(_out, it.second);
}
_out << "Equivalence classes: " << endl;
for (ExpressionClasses::Id eqClass = 0; eqClass < m_expressionClasses.size(); ++eqClass)
streamExpressionClass(_out, eqClass);
_out << "Current stack: " << endl;
for (auto const& it: _currentStack)
_out << "Initial stack: " << endl;
for (auto const& it: _initialStack)
{
_out << " " << dec << it.first << ": ";
streamExpressionClass(_out, it.second);
@ -96,9 +95,8 @@ void CommonSubexpressionEliminator::feedItem(AssemblyItem const& _item)
{
if (_item.type() != Operation)
{
if (_item.deposit() != 1)
BOOST_THROW_EXCEPTION(InvalidDeposit());
setStackElement(++m_stackHeight, m_expressionClasses.find(_item, {}));
assertThrow(_item.deposit() == 1, InvalidDeposit, "");
setStackElement(++m_stackHeight, m_expressionClasses.find(_item, {}, false));
}
else
{
@ -119,7 +117,12 @@ void CommonSubexpressionEliminator::feedItem(AssemblyItem const& _item)
vector<ExpressionClasses::Id> arguments(info.args);
for (int i = 0; i < info.args; ++i)
arguments[i] = stackElement(m_stackHeight - i);
setStackElement(m_stackHeight + _item.deposit(), m_expressionClasses.find(_item, arguments));
if (_item.instruction() == Instruction::SSTORE)
storeInStorage(arguments[0], arguments[1]);
else if (_item.instruction() == Instruction::SLOAD)
setStackElement(m_stackHeight + _item.deposit(), loadFromStorage(arguments[0]));
else
setStackElement(m_stackHeight + _item.deposit(), m_expressionClasses.find(_item, arguments, false));
}
m_stackHeight += _item.deposit();
}
@ -132,8 +135,7 @@ void CommonSubexpressionEliminator::setStackElement(int _stackHeight, Expression
void CommonSubexpressionEliminator::swapStackElements(int _stackHeightA, int _stackHeightB)
{
if (_stackHeightA == _stackHeightB)
BOOST_THROW_EXCEPTION(OptimizerException() << errinfo_comment("Swap on same stack elements."));
assertThrow(_stackHeightA != _stackHeightB, OptimizerException, "Swap on same stack elements.");
// ensure they are created
stackElement(_stackHeightA);
stackElement(_stackHeightB);
@ -157,6 +159,33 @@ ExpressionClasses::Id CommonSubexpressionEliminator::initialStackElement(int _st
return m_expressionClasses.find(AssemblyItem(dupInstruction(1 - _stackHeight)));
}
void CommonSubexpressionEliminator::storeInStorage(ExpressionClasses::Id _slot, ExpressionClasses::Id _value)
{
if (m_storageContent.count(_slot) && m_storageContent[_slot] == _value)
// do not execute the storage if we know that the value is already there
return;
m_sequenceNumber ++;
decltype(m_storageContent) storageContents;
// copy over values at points where we know that they are different from _slot
for (auto const& storageItem: m_storageContent)
if (m_expressionClasses.knownToBeDifferent(storageItem.first, _slot))
storageContents.insert(storageItem);
m_storageContent = move(storageContents);
ExpressionClasses::Id id = m_expressionClasses.find(Instruction::SSTORE, {_slot, _value}, true, m_sequenceNumber);
m_storageWrites.insert(StorageWriteOperation(_slot, m_sequenceNumber, id));
m_storageContent[_slot] = _value;
// increment a second time so that we get unique sequence numbers for writes
m_sequenceNumber ++;
}
ExpressionClasses::Id CommonSubexpressionEliminator::loadFromStorage(ExpressionClasses::Id _slot)
{
if (m_storageContent.count(_slot))
return m_storageContent.at(_slot);
else
return m_storageContent[_slot] = m_expressionClasses.find(Instruction::SLOAD, {_slot}, true, m_sequenceNumber);
}
bool SemanticInformation::breaksBasicBlock(AssemblyItem const& _item)
{
switch (_item.type())
@ -180,6 +209,8 @@ bool SemanticInformation::breaksBasicBlock(AssemblyItem const& _item)
if (_item.instruction() == Instruction::GAS || _item.instruction() == Instruction::PC)
return true; // GAS and PC assume a specific order of opcodes
InstructionInfo info = instructionInfo(_item.instruction());
if (_item.instruction() == Instruction::SSTORE)
return false;
// the second requirement will be lifted once it is implemented
return info.sideEffects || info.args > 2;
}
@ -230,26 +261,49 @@ AssemblyItems CSECodeGenerator::generateCode(
// @todo: provide information about the positions of copies of class elements
// generate the dependency graph
// generate the dependency graph starting from final storage writes and target stack contents
for (auto it = m_storageWrites.begin(); it != m_storageWrites.end();)
{
auto next = it;
++next;
if (next == m_storageWrites.end() || next->slot != it->slot)
// last write to that storage slot
addDependencies(it->expression);
it = next;
}
for (auto const& targetItem: _targetStackContents)
{
m_finalClasses.insert(targetItem.second);
addDependencies(targetItem.second);
}
// generate the actual elements
// Perform all operations on storage in order, if they are needed.
//@todo use better data structures to optimize these loops
unsigned maxSequenceNumber = 1;
for (StorageWriteOperation const& op: m_storageWrites)
maxSequenceNumber = max(maxSequenceNumber, op.sequenceNumber + 1);
for (unsigned sequenceNumber = 1; sequenceNumber <= maxSequenceNumber; ++sequenceNumber)
for (auto const& depPair: m_neededBy)
for (ExpressionClasses::Id const& id: {depPair.first, depPair.second})
if (
m_expressionClasses.representative(id).sequenceNumber == sequenceNumber &&
!m_classPositions.count(id)
)
generateClassElement(id, true);
// generate the target stack elements
for (auto const& targetItem: _targetStackContents)
{
removeStackTopIfPossible();
int position = generateClassElement(targetItem.second);
assertThrow(position != c_invalidPosition, OptimizerException, "");
if (position == targetItem.first)
continue;
if (position < targetItem.first)
// it is already at its target, we need another copy
appendDup(position);
else
appendSwapOrRemove(position);
appendSwapOrRemove(targetItem.first);
appendOrRemoveSwap(position);
appendOrRemoveSwap(targetItem.first);
}
// remove surplus elements
@ -270,23 +324,48 @@ AssemblyItems CSECodeGenerator::generateCode(
// neither initial no target stack, no change in height
finalHeight = 0;
assertThrow(finalHeight == m_stackHeight, OptimizerException, "Incorrect final stack height.");
return m_generatedItems;
}
void CSECodeGenerator::addDependencies(ExpressionClasses::Id _c)
{
if (m_neededBy.count(_c))
return;
for (ExpressionClasses::Id argument: m_expressionClasses.representative(_c).arguments)
return; // we already computed the dependencies for _c
ExpressionClasses::Expression const& expr = m_expressionClasses.representative(_c);
for (ExpressionClasses::Id argument: expr.arguments)
{
addDependencies(argument);
m_neededBy.insert(make_pair(argument, _c));
}
if (expr.item->type() == Operation && expr.item->instruction() == Instruction::SLOAD)
{
// this loads an unknown value from storage and thus, in addition to its arguments, depends
// on all SSTORE operations to addresses where we do not know that they are different that
// occur before this SLOAD
ExpressionClasses::Id slotToLoadFrom = expr.arguments.at(0);
for (auto it = m_storageWrites.begin(); it != m_storageWrites.end();)
{
auto next = it;
++next;
// note that SSTORE and SLOAD never have the same sequence number
if (it->sequenceNumber < expr.sequenceNumber &&
!m_expressionClasses.knownToBeDifferent(it->slot, slotToLoadFrom) &&
(next == m_storageWrites.end() || next->sequenceNumber > expr.sequenceNumber)
)
{
addDependencies(it->expression);
m_neededBy.insert(make_pair(it->expression, _c));
}
it = next;
}
}
}
int CSECodeGenerator::generateClassElement(ExpressionClasses::Id _c)
int CSECodeGenerator::generateClassElement(ExpressionClasses::Id _c, bool _allowSequenced)
{
// do some cleanup
removeStackTopIfPossible();
if (m_classPositions.count(_c))
{
assertThrow(
@ -296,7 +375,13 @@ int CSECodeGenerator::generateClassElement(ExpressionClasses::Id _c)
);
return m_classPositions[_c];
}
ExpressionClasses::Ids const& arguments = m_expressionClasses.representative(_c).arguments;
ExpressionClasses::Expression const& expr = m_expressionClasses.representative(_c);
assertThrow(
_allowSequenced || expr.sequenceNumber == 0,
OptimizerException,
"Sequence constrained operation requested out of sequence."
);
ExpressionClasses::Ids const& arguments = expr.arguments;
for (ExpressionClasses::Id arg: boost::adaptors::reverse(arguments))
generateClassElement(arg);
@ -307,42 +392,42 @@ int CSECodeGenerator::generateClassElement(ExpressionClasses::Id _c)
if (arguments.size() == 1)
{
if (canBeRemoved(arguments[0], _c))
appendSwapOrRemove(generateClassElement(arguments[0]));
appendOrRemoveSwap(classElementPosition(arguments[0]));
else
appendDup(generateClassElement(arguments[0]));
appendDup(classElementPosition(arguments[0]));
}
else if (arguments.size() == 2)
{
if (canBeRemoved(arguments[1], _c))
{
appendSwapOrRemove(generateClassElement(arguments[1]));
appendOrRemoveSwap(classElementPosition(arguments[1]));
if (arguments[0] == arguments[1])
appendDup(m_stackHeight);
else if (canBeRemoved(arguments[0], _c))
{
appendSwapOrRemove(m_stackHeight - 1);
appendSwapOrRemove(generateClassElement(arguments[0]));
appendOrRemoveSwap(m_stackHeight - 1);
appendOrRemoveSwap(classElementPosition(arguments[0]));
}
else
appendDup(generateClassElement(arguments[0]));
appendDup(classElementPosition(arguments[0]));
}
else
{
if (arguments[0] == arguments[1])
{
appendDup(generateClassElement(arguments[0]));
appendDup(classElementPosition(arguments[0]));
appendDup(m_stackHeight);
}
else if (canBeRemoved(arguments[0], _c))
{
appendSwapOrRemove(generateClassElement(arguments[0]));
appendDup(generateClassElement(arguments[1]));
appendSwapOrRemove(m_stackHeight - 1);
appendOrRemoveSwap(classElementPosition(arguments[0]));
appendDup(classElementPosition(arguments[1]));
appendOrRemoveSwap(m_stackHeight - 1);
}
else
{
appendDup(generateClassElement(arguments[1]));
appendDup(generateClassElement(arguments[0]));
appendDup(classElementPosition(arguments[1]));
appendDup(classElementPosition(arguments[0]));
}
}
}
@ -355,20 +440,41 @@ int CSECodeGenerator::generateClassElement(ExpressionClasses::Id _c)
for (size_t i = 0; i < arguments.size(); ++i)
assertThrow(m_stack[m_stackHeight - i] == arguments[i], OptimizerException, "Expected arguments not present." );
AssemblyItem const& item = *m_expressionClasses.representative(_c).item;
while (SemanticInformation::isCommutativeOperation(item) &&
while (SemanticInformation::isCommutativeOperation(*expr.item) &&
!m_generatedItems.empty() &&
m_generatedItems.back() == AssemblyItem(Instruction::SWAP1))
// this will not append a swap but remove the one that is already there
appendSwapOrRemove(m_stackHeight - 1);
appendOrRemoveSwap(m_stackHeight - 1);
for (auto arg: arguments)
if (canBeRemoved(arg, _c))
m_classPositions[arg] = c_invalidPosition;
for (size_t i = 0; i < arguments.size(); ++i)
m_stack.erase(m_stackHeight - i);
appendItem(*m_expressionClasses.representative(_c).item);
m_stack[m_stackHeight] = _c;
return m_classPositions[_c] = m_stackHeight;
appendItem(*expr.item);
if (expr.item->type() != Operation || instructionInfo(expr.item->instruction()).ret == 1)
{
m_stack[m_stackHeight] = _c;
return m_classPositions[_c] = m_stackHeight;
}
else
{
assertThrow(
instructionInfo(expr.item->instruction()).ret == 0,
OptimizerException,
"Invalid number of return values."
);
return m_classPositions[_c] = c_invalidPosition;
}
}
int CSECodeGenerator::classElementPosition(ExpressionClasses::Id _id) const
{
assertThrow(
m_classPositions.count(_id) && m_classPositions.at(_id) != c_invalidPosition,
OptimizerException,
"Element requested but is not present."
);
return m_classPositions.at(_id);
}
bool CSECodeGenerator::canBeRemoved(ExpressionClasses::Id _element, ExpressionClasses::Id _result)
@ -401,22 +507,23 @@ bool CSECodeGenerator::removeStackTopIfPossible()
void CSECodeGenerator::appendDup(int _fromPosition)
{
assertThrow(_fromPosition != c_invalidPosition, OptimizerException, "");
int nr = 1 + m_stackHeight - _fromPosition;
assertThrow(nr <= 16, StackTooDeepException, "Stack too deep.");
assertThrow(1 <= nr, OptimizerException, "Invalid stack access.");
m_generatedItems.push_back(AssemblyItem(dupInstruction(nr)));
m_stackHeight++;
appendItem(AssemblyItem(dupInstruction(nr)));
m_stack[m_stackHeight] = m_stack[_fromPosition];
}
void CSECodeGenerator::appendSwapOrRemove(int _fromPosition)
void CSECodeGenerator::appendOrRemoveSwap(int _fromPosition)
{
assertThrow(_fromPosition != c_invalidPosition, OptimizerException, "");
if (_fromPosition == m_stackHeight)
return;
int nr = m_stackHeight - _fromPosition;
assertThrow(nr <= 16, StackTooDeepException, "Stack too deep.");
assertThrow(1 <= nr, OptimizerException, "Invalid stack access.");
m_generatedItems.push_back(AssemblyItem(swapInstruction(nr)));
appendItem(AssemblyItem(swapInstruction(nr)));
// The value of a class can be present in multiple locations on the stack. We only update the
// "canonical" one that is tracked by m_classPositions
if (m_classPositions[m_stack[m_stackHeight]] == m_stackHeight)

67
libevmcore/CommonSubexpressionEliminator.h

@ -25,6 +25,8 @@
#include <vector>
#include <map>
#include <set>
#include <tuple>
#include <ostream>
#include <libdevcore/CommonIO.h>
#include <libdevcore/Exceptions.h>
@ -44,9 +46,9 @@ using AssemblyItems = std::vector<AssemblyItem>;
* known to be equal only once.
*
* The general workings are that for each assembly item that is fed into the eliminator, an
* equivalence class is derived from the operation and the equivalence class of its arguments and
* it is assigned to the next sequence number of a stack item. DUPi, SWAPi and some arithmetic
* instructions are used to infer equivalences while these classes are determined.
* equivalence class is derived from the operation and the equivalence class of its arguments.
* DUPi, SWAPi and some arithmetic instructions are used to infer equivalences while these
* classes are determined.
*
* When the list of optimized items is requested, they are generated in a bottom-up fashion,
* adding code for equivalence classes that were not yet computed.
@ -54,6 +56,23 @@ using AssemblyItems = std::vector<AssemblyItem>;
class CommonSubexpressionEliminator
{
public:
struct StorageWriteOperation
{
StorageWriteOperation(
ExpressionClasses::Id _slot,
unsigned _sequenceNumber,
ExpressionClasses::Id _expression
): slot(_slot), sequenceNumber(_sequenceNumber), expression(_expression) {}
bool operator<(StorageWriteOperation const& _other) const
{
return std::tie(slot, sequenceNumber, expression) <
std::tie(_other.slot, _other.sequenceNumber, _other.expression);
}
ExpressionClasses::Id slot;
unsigned sequenceNumber;
ExpressionClasses::Id expression;
};
/// Feeds AssemblyItems into the eliminator and @returns the iterator pointing at the first
/// item that must be fed into a new instance of the eliminator.
template <class _AssemblyItemIterator>
@ -65,7 +84,7 @@ public:
/// Streams debugging information to @a _out.
std::ostream& stream(
std::ostream& _out,
std::map<int, ExpressionClasses::Id> _currentStack = std::map<int, ExpressionClasses::Id>(),
std::map<int, ExpressionClasses::Id> _initialStack = std::map<int, ExpressionClasses::Id>(),
std::map<int, ExpressionClasses::Id> _targetStack = std::map<int, ExpressionClasses::Id>()
) const;
@ -85,10 +104,23 @@ private:
/// (must not be positive).
ExpressionClasses::Id initialStackElement(int _stackHeight);
/// Increments the sequence number, deletes all storage information that might be overwritten
/// and stores the new value at the given slot.
void storeInStorage(ExpressionClasses::Id _slot, ExpressionClasses::Id _value);
/// Retrieves the current value at the given slot in storage or creates a new special sload class.
ExpressionClasses::Id loadFromStorage(ExpressionClasses::Id _slot);
/// Current stack height, can be negative.
int m_stackHeight = 0;
/// Current stack layout, mapping stack height -> equivalence class
std::map<int, ExpressionClasses::Id> m_stackElements;
/// Current sequence number, this is incremented with each modification to storage.
unsigned m_sequenceNumber = 1;
/// Knowledge about storage content.
std::map<ExpressionClasses::Id, ExpressionClasses::Id> m_storageContent;
/// Keeps information about which storage slots were written to at which sequence number with
/// what SSTORE instruction.
std::set<StorageWriteOperation> m_storageWrites;
/// Structure containing the classes of equivalent expressions.
ExpressionClasses m_expressionClasses;
};
@ -114,14 +146,19 @@ struct SemanticInformation
class CSECodeGenerator
{
public:
CSECodeGenerator(ExpressionClasses const& _expressionClasses):
m_expressionClasses(_expressionClasses)
using StorageWriteOperation = CommonSubexpressionEliminator::StorageWriteOperation;
CSECodeGenerator(
ExpressionClasses& _expressionClasses,
std::set<StorageWriteOperation> const& _storageWrites
):
m_expressionClasses(_expressionClasses),
m_storageWrites(_storageWrites)
{}
/// @returns the assembly items generated from the given requirements
/// @param _initialStack current contents of the stack (up to stack height of zero)
/// @param _targetStackContents final contents of the stack, by stack height relative to initial
/// @param _equivalenceClasses equivalence classes as expressions of how to compute them
/// @note should only be called once on each object.
AssemblyItems generateCode(
std::map<int, ExpressionClasses::Id> const& _initialStack,
@ -133,8 +170,13 @@ private:
void addDependencies(ExpressionClasses::Id _c);
/// Produce code that generates the given element if it is not yet present.
/// @returns the stack position of the element.
int generateClassElement(ExpressionClasses::Id _c);
/// @returns the stack position of the element or c_invalidPosition if it does not actually
/// generate a value on the stack.
/// @param _allowSequenced indicates that sequence-constrained operations are allowed
int generateClassElement(ExpressionClasses::Id _c, bool _allowSequenced = false);
/// @returns the position of the representative of the given id on the stack.
/// @note throws an exception if it is not on the stack.
int classElementPosition(ExpressionClasses::Id _id) const;
/// @returns true if @a _element can be removed - in general or, if given, while computing @a _result.
bool canBeRemoved(ExpressionClasses::Id _element, ExpressionClasses::Id _result = ExpressionClasses::Id(-1));
@ -146,7 +188,7 @@ private:
void appendDup(int _fromPosition);
/// Appends a swap instruction to m_generatedItems to retrieve the element at the given stack position.
/// @note this might also remove the last item if it exactly the same swap instruction.
void appendSwapOrRemove(int _fromPosition);
void appendOrRemoveSwap(int _fromPosition);
/// Appends the given assembly item.
void appendItem(AssemblyItem const& _item);
@ -163,7 +205,10 @@ private:
std::map<ExpressionClasses::Id, int> m_classPositions;
/// The actual eqivalence class items and how to compute them.
ExpressionClasses const& m_expressionClasses;
ExpressionClasses& m_expressionClasses;
/// Keeps information about which storage slots were written to at which sequence number with
/// what SSTORE instruction.
std::set<StorageWriteOperation> const& m_storageWrites;
/// The set of equivalence classes that should be present on the stack at the end.
std::set<ExpressionClasses::Id> m_finalClasses;
};

70
libevmcore/ExpressionClasses.cpp

@ -39,16 +39,22 @@ bool ExpressionClasses::Expression::operator<(ExpressionClasses::Expression cons
{
auto type = item->type();
auto otherType = _other.item->type();
return std::tie(type, item->data(), arguments) <
std::tie(otherType, _other.item->data(), _other.arguments);
return std::tie(type, item->data(), arguments, sequenceNumber) <
std::tie(otherType, _other.item->data(), _other.arguments, _other.sequenceNumber);
}
ExpressionClasses::Id ExpressionClasses::find(AssemblyItem const& _item, Ids const& _arguments)
ExpressionClasses::Id ExpressionClasses::find(
AssemblyItem const& _item,
Ids const& _arguments,
bool _copyItem,
unsigned _sequenceNumber
)
{
Expression exp;
exp.id = Id(-1);
exp.item = &_item;
exp.arguments = _arguments;
exp.sequenceNumber = _sequenceNumber;
if (SemanticInformation::isCommutativeOperation(_item))
sort(exp.arguments.begin(), exp.arguments.end());
@ -58,9 +64,8 @@ ExpressionClasses::Id ExpressionClasses::find(AssemblyItem const& _item, Ids con
if (!(e < exp || exp < e))
return e.id;
if (SemanticInformation::isDupInstruction(_item))
if (_copyItem)
{
// Special item that refers to values pre-existing on the stack
m_spareAssemblyItem.push_back(make_shared<AssemblyItem>(_item));
exp.item = m_spareAssemblyItem.back().get();
}
@ -74,6 +79,17 @@ ExpressionClasses::Id ExpressionClasses::find(AssemblyItem const& _item, Ids con
return exp.id;
}
bool ExpressionClasses::knownToBeDifferent(ExpressionClasses::Id _a, ExpressionClasses::Id _b)
{
// Try to simplify "_a - _b" and return true iff the value is a non-zero constant.
//@todo we could try to cache this information
map<unsigned, Expression const*> matchGroups;
Pattern constant(Push);
constant.setMatchGroup(1, matchGroups);
Id difference = find(Instruction::SUB, {_a, _b});
return constant.matches(representative(difference), *this) && constant.d() != u256(0);
}
string ExpressionClasses::fullDAGToString(ExpressionClasses::Id _id) const
{
Expression const& expr = representative(_id);
@ -189,27 +205,46 @@ Rules::Rules()
// Moving constants to the outside, order matters here!
// we need actions that return expressions (or patterns?) here, and we need also reversed rules
// (X+A)+B -> X+(A+B)
m_rules.push_back({
m_rules += vector<pair<Pattern, function<Pattern()>>>{{
{op, {{op, {X, A}}, B}},
[=]() -> Pattern { return {op, {X, fun(A.d(), B.d())}}; }
});
}, {
// X+(Y+A) -> (X+Y)+A
m_rules.push_back({
{op, {{op, {X, A}}, Y}},
[=]() -> Pattern { return {op, {{op, {X, Y}}, A}}; }
});
}, {
// For now, we still need explicit commutativity for the inner pattern
m_rules.push_back({
{op, {{op, {A, X}}, B}},
[=]() -> Pattern { return {op, {X, fun(A.d(), B.d())}}; }
});
m_rules.push_back({
}, {
{op, {{op, {A, X}}, Y}},
[=]() -> Pattern { return {op, {{op, {X, Y}}, A}}; }
});
}};
}
// move constants across subtractions
m_rules += vector<pair<Pattern, function<Pattern()>>>{
{
// X - A -> X + (-A)
{Instruction::SUB, {X, A}},
[=]() -> Pattern { return {Instruction::ADD, {X, 0 - A.d()}}; }
}, {
// (X + A) - Y -> (X - Y) + A
{Instruction::SUB, {{Instruction::ADD, {X, A}}, Y}},
[=]() -> Pattern { return {Instruction::ADD, {{Instruction::SUB, {X, Y}}, A}}; }
}, {
// (A + X) - Y -> (X - Y) + A
{Instruction::SUB, {{Instruction::ADD, {A, X}}, Y}},
[=]() -> Pattern { return {Instruction::ADD, {{Instruction::SUB, {X, Y}}, A}}; }
}, {
// X - (Y + A) -> (X - Y) + (-A)
{Instruction::SUB, {X, {Instruction::ADD, {Y, A}}}},
[=]() -> Pattern { return {Instruction::ADD, {{Instruction::SUB, {X, Y}}, 0 - A.d()}}; }
}, {
// X - (A + Y) -> (X - Y) + (-A)
{Instruction::SUB, {X, {Instruction::ADD, {A, Y}}}},
[=]() -> Pattern { return {Instruction::ADD, {{Instruction::SUB, {X, Y}}, 0 - A.d()}}; }
}
};
//@todo: (x+8)-3 and other things
}
ExpressionClasses::Id ExpressionClasses::tryToSimplify(Expression const& _expr, bool _secondRun)
@ -231,7 +266,7 @@ ExpressionClasses::Id ExpressionClasses::tryToSimplify(Expression const& _expr,
//cout << ")" << endl;
//cout << "with rule " << rule.first.toString() << endl;
//ExpressionTemplate t(rule.second());
//cout << "to" << rule.second().toString() << endl;
//cout << "to " << rule.second().toString() << endl;
return rebuildExpression(ExpressionTemplate(rule.second()));
}
}
@ -254,8 +289,7 @@ ExpressionClasses::Id ExpressionClasses::rebuildExpression(ExpressionTemplate co
Ids arguments;
for (ExpressionTemplate const& t: _template.arguments)
arguments.push_back(rebuildExpression(t));
m_spareAssemblyItem.push_back(make_shared<AssemblyItem>(_template.item));
return find(*m_spareAssemblyItem.back(), arguments);
return find(_template.item, arguments);
}

10
libevmcore/ExpressionClasses.h

@ -52,17 +52,25 @@ public:
Id id;
AssemblyItem const* item;
Ids arguments;
unsigned sequenceNumber; ///< Storage modification sequence, only used for SLOAD/SSTORE instructions.
bool operator<(Expression const& _other) const;
};
/// Retrieves the id of the expression equivalence class resulting from the given item applied to the
/// given classes, might also create a new one.
Id find(AssemblyItem const& _item, Ids const& _arguments = {});
/// @param _copyItem if true, copies the assembly item to an internal storage instead of just
/// keeping a pointer.
/// The @a _sequenceNumber indicates the current storage access sequence.
Id find(AssemblyItem const& _item, Ids const& _arguments = {}, bool _copyItem = true, unsigned _sequenceNumber = 0);
/// @returns the canonical representative of an expression class.
Expression const& representative(Id _id) const { return m_representatives.at(_id); }
/// @returns the number of classes.
Id size() const { return m_representatives.size(); }
/// @returns true if the values of the given classes are known to be different (on every input).
/// @note that this function might still return false for some different inputs.
bool knownToBeDifferent(Id _a, Id _b);
std::string fullDAGToString(Id _id) const;
private:

180
test/SolidityOptimizer.cpp

@ -303,6 +303,186 @@ BOOST_AUTO_TEST_CASE(cse_associativity2)
checkCSE(input, {Instruction::DUP2, Instruction::DUP2, Instruction::ADD, u256(5), Instruction::ADD});
}
BOOST_AUTO_TEST_CASE(cse_storage)
{
AssemblyItems input{
u256(0),
Instruction::SLOAD,
u256(0),
Instruction::SLOAD,
Instruction::ADD,
u256(0),
Instruction::SSTORE
};
checkCSE(input, {
u256(0),
Instruction::DUP1,
Instruction::SLOAD,
Instruction::DUP1,
Instruction::ADD,
Instruction::SWAP1,
Instruction::SSTORE
});
}
BOOST_AUTO_TEST_CASE(cse_noninterleaved_storage)
{
// two stores to the same location should be replaced by only one store, even if we
// read in the meantime
AssemblyItems input{
u256(7),
Instruction::DUP2,
Instruction::SSTORE,
Instruction::DUP1,
Instruction::SLOAD,
u256(8),
Instruction::DUP3,
Instruction::SSTORE
};
checkCSE(input, {
u256(8),
Instruction::DUP2,
Instruction::SSTORE,
u256(7)
});
}
BOOST_AUTO_TEST_CASE(cse_interleaved_storage)
{
// stores and reads to/from two unknown locations, should not optimize away the first store
AssemblyItems input{
u256(7),
Instruction::DUP2,
Instruction::SSTORE, // store to "DUP1"
Instruction::DUP2,
Instruction::SLOAD, // read from "DUP2", might be equal to "DUP1"
u256(0),
Instruction::DUP3,
Instruction::SSTORE // store different value to "DUP1"
};
checkCSE(input, input);
}
BOOST_AUTO_TEST_CASE(cse_interleaved_storage_same_value)
{
// stores and reads to/from two unknown locations, should not optimize away the first store
// but it should optimize away the second, since we already know the value will be the same
AssemblyItems input{
u256(7),
Instruction::DUP2,
Instruction::SSTORE, // store to "DUP1"
Instruction::DUP2,
Instruction::SLOAD, // read from "DUP2", might be equal to "DUP1"
u256(6),
u256(1),
Instruction::ADD,
Instruction::DUP3,
Instruction::SSTORE // store same value to "DUP1"
};
checkCSE(input, {
u256(7),
Instruction::DUP2,
Instruction::SSTORE,
Instruction::DUP2,
Instruction::SLOAD
});
}
BOOST_AUTO_TEST_CASE(cse_interleaved_storage_at_known_location)
{
// stores and reads to/from two known locations, should optimize away the first store,
// because we know that the location is different
AssemblyItems input{
u256(0x70),
u256(1),
Instruction::SSTORE, // store to 1
u256(2),
Instruction::SLOAD, // read from 2, is different from 1
u256(0x90),
u256(1),
Instruction::SSTORE // store different value at 1
};
checkCSE(input, {
u256(2),
Instruction::SLOAD,
u256(0x90),
u256(1),
Instruction::SSTORE
});
}
BOOST_AUTO_TEST_CASE(cse_interleaved_storage_at_known_location_offset)
{
// stores and reads to/from two locations which are known to be different,
// should optimize away the first store, because we know that the location is different
AssemblyItems input{
u256(0x70),
Instruction::DUP2,
u256(1),
Instruction::ADD,
Instruction::SSTORE, // store to "DUP1"+1
Instruction::DUP1,
u256(2),
Instruction::ADD,
Instruction::SLOAD, // read from "DUP1"+2, is different from "DUP1"+1
u256(0x90),
Instruction::DUP3,
u256(1),
Instruction::ADD,
Instruction::SSTORE // store different value at "DUP1"+1
};
checkCSE(input, {
u256(2),
Instruction::DUP2,
Instruction::ADD,
Instruction::SLOAD,
u256(0x90),
u256(1),
Instruction::DUP4,
Instruction::ADD,
Instruction::SSTORE
});
}
BOOST_AUTO_TEST_CASE(cse_deep_stack)
{
AssemblyItems input{
Instruction::ADD,
Instruction::SWAP1,
Instruction::POP,
Instruction::SWAP8,
Instruction::POP,
Instruction::SWAP8,
Instruction::POP,
Instruction::SWAP8,
Instruction::SWAP5,
Instruction::POP,
Instruction::POP,
Instruction::POP,
Instruction::POP,
Instruction::POP,
};
checkCSE(input, {
Instruction::SWAP4,
Instruction::SWAP12,
Instruction::SWAP3,
Instruction::SWAP11,
Instruction::POP,
Instruction::SWAP1,
Instruction::SWAP3,
Instruction::ADD,
Instruction::SWAP8,
Instruction::POP,
Instruction::SWAP6,
Instruction::POP,
Instruction::POP,
Instruction::POP,
Instruction::POP,
Instruction::POP,
Instruction::POP,
});
}
BOOST_AUTO_TEST_SUITE_END()
}

Loading…
Cancel
Save