Browse Source

Merge pull request #1432 from chriseth/sol_cseForMemoryAndStorage

Optimizer for memory and storage.
cl-refactor
chriseth 10 years ago
parent
commit
bad7a56e5a
  1. 15
      libevmcore/Assembly.cpp
  2. 303
      libevmcore/CommonSubexpressionEliminator.cpp
  3. 84
      libevmcore/CommonSubexpressionEliminator.h
  4. 100
      libevmcore/ExpressionClasses.cpp
  5. 20
      libevmcore/ExpressionClasses.h
  6. 265
      test/SolidityOptimizer.cpp

15
libevmcore/Assembly.cpp

@ -187,18 +187,7 @@ Assembly& Assembly::optimise(bool _enable)
{
if (!_enable)
return *this;
std::vector<pair<AssemblyItems, function<AssemblyItems(AssemblyItemsConstRef)>>> rules =
{
{ { Push, Instruction::POP }, [](AssemblyItemsConstRef) -> AssemblyItems { return {}; } },
{ { PushTag, Instruction::POP }, [](AssemblyItemsConstRef) -> AssemblyItems { return {}; } },
{ { PushString, Instruction::POP }, [](AssemblyItemsConstRef) -> AssemblyItems { return {}; } },
{ { PushSub, Instruction::POP }, [](AssemblyItemsConstRef) -> AssemblyItems { return {}; } },
{ { PushSubSize, Instruction::POP }, [](AssemblyItemsConstRef) -> AssemblyItems { return {}; } },
{ { PushProgramSize, Instruction::POP }, [](AssemblyItemsConstRef) -> AssemblyItems { return {}; } },
{ { Push, PushTag, Instruction::JUMPI }, [](AssemblyItemsConstRef m) -> AssemblyItems { if (m[0].data()) return { m[1], Instruction::JUMP }; else return {}; } },
{ { Instruction::ISZERO, Instruction::ISZERO }, [](AssemblyItemsConstRef) -> AssemblyItems { return {}; } },
};
std::vector<pair<AssemblyItems, function<AssemblyItems(AssemblyItemsConstRef)>>> rules;
// jump to next instruction
rules.push_back({ { PushTag, Instruction::JUMP, Tag }, [](AssemblyItemsConstRef m) -> AssemblyItems { if (m[0].m_data == m[2].m_data) return {m[2]}; else return m.toVector(); }});
@ -235,8 +224,6 @@ Assembly& Assembly::optimise(bool _enable)
*orig = move(*moveIter);
iter = m_items.erase(orig, iter);
}
if (iter != m_items.end())
++iter;
}
for (unsigned i = 0; i < m_items.size(); ++i)

303
libevmcore/CommonSubexpressionEliminator.cpp

@ -32,6 +32,8 @@ using namespace dev::eth;
vector<AssemblyItem> CommonSubexpressionEliminator::getOptimizedItems()
{
optimizeBreakingItem();
map<int, ExpressionClasses::Id> initialStackContents;
map<int, ExpressionClasses::Id> targetStackContents;
int minHeight = m_stackHeight + 1;
@ -45,19 +47,27 @@ vector<AssemblyItem> CommonSubexpressionEliminator::getOptimizedItems()
// Debug info:
//stream(cout, initialStackContents, targetStackContents);
return CSECodeGenerator(m_expressionClasses).generateCode(initialStackContents, targetStackContents);
AssemblyItems items = CSECodeGenerator(m_expressionClasses, m_storeOperations).generateCode(
initialStackContents,
targetStackContents
);
if (m_breakingItem)
items.push_back(*m_breakingItem);
return items;
}
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 +76,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);
@ -92,13 +96,12 @@ ostream& CommonSubexpressionEliminator::stream(
return _out;
}
void CommonSubexpressionEliminator::feedItem(AssemblyItem const& _item)
void CommonSubexpressionEliminator::feedItem(AssemblyItem const& _item, bool _copyItem)
{
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, {}, _copyItem));
}
else
{
@ -119,12 +122,47 @@ 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 if (_item.instruction() == Instruction::MSTORE)
storeInMemory(arguments[0], arguments[1]);
else if (_item.instruction() == Instruction::MLOAD)
setStackElement(m_stackHeight + _item.deposit(), loadFromMemory(arguments[0]));
else
setStackElement(m_stackHeight + _item.deposit(), m_expressionClasses.find(_item, arguments, _copyItem));
}
m_stackHeight += _item.deposit();
}
}
void CommonSubexpressionEliminator::optimizeBreakingItem()
{
if (!m_breakingItem || *m_breakingItem != AssemblyItem(Instruction::JUMPI))
return;
using Id = ExpressionClasses::Id;
static AssemblyItem s_jump = Instruction::JUMP;
Id condition = stackElement(m_stackHeight - 1);
Id zero = m_expressionClasses.find(u256(0));
if (m_expressionClasses.knownToBeDifferent(condition, zero))
{
feedItem(Instruction::SWAP1, true);
feedItem(Instruction::POP, true);
m_breakingItem = &s_jump;
return;
}
Id negatedCondition = m_expressionClasses.find(Instruction::ISZERO, {condition});
if (m_expressionClasses.knownToBeDifferent(negatedCondition, zero))
{
feedItem(Instruction::POP, true);
feedItem(Instruction::POP, true);
m_breakingItem = nullptr;
}
}
void CommonSubexpressionEliminator::setStackElement(int _stackHeight, ExpressionClasses::Id _class)
{
m_stackElements[_stackHeight] = _class;
@ -132,8 +170,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 +194,60 @@ 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_storeOperations.push_back(StoreOperation(StoreOperation::Storage, _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);
}
void CommonSubexpressionEliminator::storeInMemory(ExpressionClasses::Id _slot, ExpressionClasses::Id _value)
{
if (m_memoryContent.count(_slot) && m_memoryContent[_slot] == _value)
// do not execute the store if we know that the value is already there
return;
m_sequenceNumber++;
decltype(m_memoryContent) memoryContents;
// copy over values at points where we know that they are different from _slot by at least 32
for (auto const& memoryItem: m_memoryContent)
if (m_expressionClasses.knownToBeDifferentBy32(memoryItem.first, _slot))
memoryContents.insert(memoryItem);
m_memoryContent = move(memoryContents);
ExpressionClasses::Id id = m_expressionClasses.find(Instruction::MSTORE, {_slot, _value}, true, m_sequenceNumber);
m_storeOperations.push_back(StoreOperation(StoreOperation::Memory, _slot, m_sequenceNumber, id));
m_memoryContent[_slot] = _value;
// increment a second time so that we get unique sequence numbers for writes
m_sequenceNumber++;
}
ExpressionClasses::Id CommonSubexpressionEliminator::loadFromMemory(ExpressionClasses::Id _slot)
{
if (m_memoryContent.count(_slot))
return m_memoryContent.at(_slot);
else
return m_memoryContent[_slot] = m_expressionClasses.find(Instruction::MLOAD, {_slot}, true, m_sequenceNumber);
}
bool SemanticInformation::breaksBasicBlock(AssemblyItem const& _item)
{
switch (_item.type())
@ -179,7 +270,19 @@ bool SemanticInformation::breaksBasicBlock(AssemblyItem const& _item)
return false;
if (_item.instruction() == Instruction::GAS || _item.instruction() == Instruction::PC)
return true; // GAS and PC assume a specific order of opcodes
if (_item.instruction() == Instruction::MSIZE)
return true; // msize is modified already by memory access, avoid that for now
if (_item.instruction() == Instruction::SHA3)
return true; //@todo: we have to compare sha3's not based on their memory addresses but on the memory content.
InstructionInfo info = instructionInfo(_item.instruction());
if (_item.instruction() == Instruction::SSTORE)
return false;
if (_item.instruction() == Instruction::MSTORE)
return false;
//@todo: We do not handle the following memory instructions for now:
// calldatacopy, codecopy, extcodecopy, mstore8,
// msize (note that msize also depends on memory read access)
// the second requirement will be lifted once it is implemented
return info.sideEffects || info.args > 2;
}
@ -218,6 +321,16 @@ bool SemanticInformation::isSwapInstruction(AssemblyItem const& _item)
return Instruction::SWAP1 <= _item.instruction() && _item.instruction() <= Instruction::SWAP16;
}
CSECodeGenerator::CSECodeGenerator(
ExpressionClasses& _expressionClasses,
vector<CSECodeGenerator::StoreOperation> const& _storeOperations
):
m_expressionClasses(_expressionClasses)
{
for (auto const& store: _storeOperations)
m_storeOperations[make_pair(store.target, store.slot)].push_back(store);
}
AssemblyItems CSECodeGenerator::generateCode(
map<int, ExpressionClasses::Id> const& _initialStack,
map<int, ExpressionClasses::Id> const& _targetStackContents
@ -230,26 +343,40 @@ 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 and memory writes and target stack contents
for (auto const& p: m_storeOperations)
addDependencies(p.second.back().expression);
for (auto const& targetItem: _targetStackContents)
{
m_finalClasses.insert(targetItem.second);
addDependencies(targetItem.second);
}
// generate the actual elements
// store all needed sequenced expressions
set<pair<unsigned, ExpressionClasses::Id>> sequencedExpressions;
for (auto const& p: m_neededBy)
for (auto id: {p.first, p.second})
if (unsigned seqNr = m_expressionClasses.representative(id).sequenceNumber)
sequencedExpressions.insert(make_pair(seqNr, id));
// Perform all operations on storage and memory in order, if they are needed.
for (auto const& seqAndId: sequencedExpressions)
if (!m_classPositions.count(seqAndId.second))
generateClassElement(seqAndId.second, 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 +397,59 @@ 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 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 ||
expr.item->instruction() == Instruction::MLOAD
))
{
// this loads an unknown value from storage or memory and thus, in addition to its
// arguments, depends on all store operations to addresses where we do not know that
// they are different that occur before this load
StoreOperation::Target target = expr.item->instruction() == Instruction::SLOAD ?
StoreOperation::Storage : StoreOperation::Memory;
ExpressionClasses::Id slotToLoadFrom = expr.arguments.at(0);
for (auto const& p: m_storeOperations)
{
if (p.first.first != target)
continue;
ExpressionClasses::Id slot = p.first.second;
StoreOperations const& storeOps = p.second;
if (storeOps.front().sequenceNumber > expr.sequenceNumber)
continue;
if (
(target == StoreOperation::Memory && m_expressionClasses.knownToBeDifferentBy32(slot, slotToLoadFrom)) ||
(target == StoreOperation::Storage && m_expressionClasses.knownToBeDifferent(slot, slotToLoadFrom))
)
continue;
// note that store and load never have the same sequence number
ExpressionClasses::Id latestStore = storeOps.front().expression;
for (auto it = ++storeOps.begin(); it != storeOps.end(); ++it)
if (it->sequenceNumber < expr.sequenceNumber)
latestStore = it->expression;
addDependencies(latestStore);
m_neededBy.insert(make_pair(latestStore, _c));
}
}
}
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 +459,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 +476,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 +524,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 +591,23 @@ bool CSECodeGenerator::removeStackTopIfPossible()
void CSECodeGenerator::appendDup(int _fromPosition)
{
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++;
assertThrow(_fromPosition != c_invalidPosition, OptimizerException, "");
int instructionNum = 1 + m_stackHeight - _fromPosition;
assertThrow(instructionNum <= 16, StackTooDeepException, "Stack too deep.");
assertThrow(1 <= instructionNum, OptimizerException, "Invalid stack access.");
appendItem(AssemblyItem(dupInstruction(instructionNum)));
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)));
int instructionNum = m_stackHeight - _fromPosition;
assertThrow(instructionNum <= 16, StackTooDeepException, "Stack too deep.");
assertThrow(1 <= instructionNum, OptimizerException, "Invalid stack access.");
appendItem(AssemblyItem(swapInstruction(instructionNum)));
// 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)

84
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,21 @@ using AssemblyItems = std::vector<AssemblyItem>;
class CommonSubexpressionEliminator
{
public:
struct StoreOperation
{
enum Target { Memory, Storage };
StoreOperation(
Target _target,
ExpressionClasses::Id _slot,
unsigned _sequenceNumber,
ExpressionClasses::Id _expression
): target(_target), slot(_slot), sequenceNumber(_sequenceNumber), expression(_expression) {}
Target target;
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,13 +82,16 @@ 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;
private:
/// Feeds the item into the system for analysis.
void feedItem(AssemblyItem const& _item);
void feedItem(AssemblyItem const& _item, bool _copyItem = false);
/// Tries to optimize the item that breaks the basic block at the end.
void optimizeBreakingItem();
/// Simplifies the given item using
/// Assigns a new equivalence class to the next sequence number of the given stack element.
@ -85,12 +105,38 @@ 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);
/// Increments the sequence number, deletes all memory information that might be overwritten
/// and stores the new value at the given slot.
void storeInMemory(ExpressionClasses::Id _slot, ExpressionClasses::Id _value);
/// Retrieves the current value at the given slot in memory or creates a new special mload class.
ExpressionClasses::Id loadFromMemory(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 or memory.
unsigned m_sequenceNumber = 1;
/// Knowledge about storage content.
std::map<ExpressionClasses::Id, ExpressionClasses::Id> m_storageContent;
/// Knowledge about memory content. Keys are memory addresses, note that the values overlap
/// and are not contained here if they are not completely known.
std::map<ExpressionClasses::Id, ExpressionClasses::Id> m_memoryContent;
/// Keeps information about which storage or memory slots were written to at which sequence
/// number with what instruction.
std::vector<StoreOperation> m_storeOperations;
/// Structure containing the classes of equivalent expressions.
ExpressionClasses m_expressionClasses;
/// The item that breaks the basic block, can be nullptr.
/// It is usually appended to the block but can be optimized in some cases.
AssemblyItem const* m_breakingItem = nullptr;
};
/**
@ -114,14 +160,16 @@ struct SemanticInformation
class CSECodeGenerator
{
public:
CSECodeGenerator(ExpressionClasses const& _expressionClasses):
m_expressionClasses(_expressionClasses)
{}
using StoreOperation = CommonSubexpressionEliminator::StoreOperation;
using StoreOperations = std::vector<StoreOperation>;
/// Initializes the code generator with the given classes and store operations.
/// The store operations have to be sorted by sequence number in ascending order.
CSECodeGenerator(ExpressionClasses& _expressionClasses, StoreOperations const& _storeOperations);
/// @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 +181,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 +199,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 +216,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 or memory slots were written to by which operations.
/// The operations are sorted ascendingly by sequence number.
std::map<std::pair<StoreOperation::Target, ExpressionClasses::Id>, StoreOperations> m_storeOperations;
/// The set of equivalence classes that should be present on the stack at the end.
std::set<ExpressionClasses::Id> m_finalClasses;
};
@ -176,6 +232,8 @@ _AssemblyItemIterator CommonSubexpressionEliminator::feedItems(
{
for (; _iterator != _end && !SemanticInformation::breaksBasicBlock(*_iterator); ++_iterator)
feedItem(*_iterator);
if (_iterator != _end)
m_breakingItem = &(*_iterator++);
return _iterator;
}

100
libevmcore/ExpressionClasses.cpp

@ -39,41 +39,71 @@ 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());
//@todo store all class members (not only the representatives) in an efficient data structure to search here
for (Expression const& e: m_representatives)
if (!(e < exp || exp < e))
return e.id;
auto it = m_expressions.find(exp);
if (it != m_expressions.end())
return it->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();
}
ExpressionClasses::Id id = tryToSimplify(exp);
if (id < m_representatives.size())
return id;
exp.id = m_representatives.size();
m_representatives.push_back(exp);
exp.id = id;
else
{
exp.id = m_representatives.size();
m_representatives.push_back(exp);
}
m_expressions.insert(exp);
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.
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);
}
bool ExpressionClasses::knownToBeDifferentBy32(ExpressionClasses::Id _a, ExpressionClasses::Id _b)
{
// Try to simplify "_a - _b" and return true iff the value is at least 32 away from zero.
map<unsigned, Expression const*> matchGroups;
Pattern constant(Push);
constant.setMatchGroup(1, matchGroups);
Id difference = find(Instruction::SUB, {_a, _b});
if (!constant.matches(representative(difference), *this))
return false;
// forbidden interval is ["-31", 31]
return constant.d() + 31 > u256(62);
}
string ExpressionClasses::fullDAGToString(ExpressionClasses::Id _id) const
{
Expression const& expr = representative(_id);
@ -189,27 +219,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 +280,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 +303,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);
}

20
libevmcore/ExpressionClasses.h

@ -52,17 +52,33 @@ public:
Id id;
AssemblyItem const* item;
Ids arguments;
unsigned sequenceNumber; ///< Storage modification sequence, only used for SLOAD/SSTORE instructions.
/// Behaves as if this was a tuple of (item->type(), item->data(), arguments, sequenceNumber).
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 or memory 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);
/// Similar to @a knownToBeDifferent but require that abs(_a - b) >= 32.
bool knownToBeDifferentBy32(Id _a, Id _b);
std::string fullDAGToString(Id _id) const;
private:
@ -78,6 +94,8 @@ private:
/// Expression equivalence class representatives - we only store one item of an equivalence.
std::vector<Expression> m_representatives;
/// All expression ever encountered.
std::set<Expression> m_expressions;
std::vector<std::shared_ptr<AssemblyItem>> m_spareAssemblyItem;
};

265
test/SolidityOptimizer.cpp

@ -303,6 +303,271 @@ 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_interleaved_memory_at_known_location_offset)
{
// stores and reads to/from two locations which are known to be different,
// should not optimize away the first store, because the location overlaps with the load,
// but it should optimize away the second, because we know that the location is different by 32
AssemblyItems input{
u256(0x50),
Instruction::DUP2,
u256(2),
Instruction::ADD,
Instruction::MSTORE, // ["DUP1"+2] = 0x50
u256(0x60),
Instruction::DUP2,
u256(32),
Instruction::ADD,
Instruction::MSTORE, // ["DUP1"+32] = 0x60
Instruction::DUP1,
Instruction::MLOAD, // read from "DUP1"
u256(0x70),
Instruction::DUP3,
u256(32),
Instruction::ADD,
Instruction::MSTORE, // ["DUP1"+32] = 0x70
u256(0x80),
Instruction::DUP3,
u256(2),
Instruction::ADD,
Instruction::MSTORE, // ["DUP1"+2] = 0x80
};
// If the actual code changes too much, we could also simply check that the output contains
// exactly 3 MSTORE and exactly 1 MLOAD instruction.
checkCSE(input, {
u256(0x50),
u256(2),
Instruction::DUP3,
Instruction::ADD,
Instruction::SWAP1,
Instruction::DUP2,
Instruction::MSTORE, // ["DUP1"+2] = 0x50
Instruction::DUP2,
Instruction::MLOAD, // read from "DUP1"
u256(0x70),
u256(32),
Instruction::DUP5,
Instruction::ADD,
Instruction::MSTORE, // ["DUP1"+32] = 0x70
u256(0x80),
Instruction::SWAP1,
Instruction::SWAP2,
Instruction::MSTORE // ["DUP1"+2] = 0x80
});
}
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_CASE(cse_jumpi_no_jump)
{
AssemblyItems input{
u256(0),
u256(1),
Instruction::DUP2,
AssemblyItem(PushTag, 1),
Instruction::JUMPI
};
checkCSE(input, {
u256(0),
u256(1)
});
}
BOOST_AUTO_TEST_CASE(cse_jumpi_jump)
{
AssemblyItems input{
u256(1),
u256(1),
Instruction::DUP2,
AssemblyItem(PushTag, 1),
Instruction::JUMPI
};
checkCSE(input, {
u256(1),
Instruction::DUP1,
AssemblyItem(PushTag, 1),
Instruction::JUMP
});
}
BOOST_AUTO_TEST_SUITE_END()
}

Loading…
Cancel
Save