|
|
@ -23,6 +23,7 @@ |
|
|
|
|
|
|
|
#include <functional> |
|
|
|
#include <boost/range/adaptor/reversed.hpp> |
|
|
|
#include <libdevcrypto/SHA3.h> |
|
|
|
#include <libevmcore/CommonSubexpressionEliminator.h> |
|
|
|
#include <libevmcore/AssemblyItem.h> |
|
|
|
|
|
|
@ -34,8 +35,8 @@ vector<AssemblyItem> CommonSubexpressionEliminator::getOptimizedItems() |
|
|
|
{ |
|
|
|
optimizeBreakingItem(); |
|
|
|
|
|
|
|
map<int, ExpressionClasses::Id> initialStackContents; |
|
|
|
map<int, ExpressionClasses::Id> targetStackContents; |
|
|
|
map<int, Id> initialStackContents; |
|
|
|
map<int, Id> targetStackContents; |
|
|
|
int minHeight = m_stackHeight + 1; |
|
|
|
if (!m_stackElements.empty()) |
|
|
|
minHeight = min(minHeight, m_stackElements.begin()->first); |
|
|
@ -58,18 +59,18 @@ vector<AssemblyItem> CommonSubexpressionEliminator::getOptimizedItems() |
|
|
|
|
|
|
|
ostream& CommonSubexpressionEliminator::stream( |
|
|
|
ostream& _out, |
|
|
|
map<int, ExpressionClasses::Id> _initialStack, |
|
|
|
map<int, ExpressionClasses::Id> _targetStack |
|
|
|
map<int, Id> _initialStack, |
|
|
|
map<int, Id> _targetStack |
|
|
|
) const |
|
|
|
{ |
|
|
|
auto streamExpressionClass = [this](ostream& _out, ExpressionClasses::Id _id) |
|
|
|
auto streamExpressionClass = [this](ostream& _out, Id _id) |
|
|
|
{ |
|
|
|
auto const& expr = m_expressionClasses.representative(_id); |
|
|
|
_out << " " << dec << _id << ": " << *expr.item; |
|
|
|
if (expr.sequenceNumber) |
|
|
|
_out << "@" << dec << expr.sequenceNumber; |
|
|
|
_out << "("; |
|
|
|
for (ExpressionClasses::Id arg: expr.arguments) |
|
|
|
for (Id arg: expr.arguments) |
|
|
|
_out << dec << arg << ","; |
|
|
|
_out << ")" << endl; |
|
|
|
}; |
|
|
@ -77,7 +78,7 @@ ostream& CommonSubexpressionEliminator::stream( |
|
|
|
_out << "Optimizer analysis:" << endl; |
|
|
|
_out << "Final stack height: " << dec << m_stackHeight << endl; |
|
|
|
_out << "Equivalence classes: " << endl; |
|
|
|
for (ExpressionClasses::Id eqClass = 0; eqClass < m_expressionClasses.size(); ++eqClass) |
|
|
|
for (Id eqClass = 0; eqClass < m_expressionClasses.size(); ++eqClass) |
|
|
|
streamExpressionClass(_out, eqClass); |
|
|
|
|
|
|
|
_out << "Initial stack: " << endl; |
|
|
@ -119,7 +120,7 @@ void CommonSubexpressionEliminator::feedItem(AssemblyItem const& _item, bool _co |
|
|
|
); |
|
|
|
else if (instruction != Instruction::POP) |
|
|
|
{ |
|
|
|
vector<ExpressionClasses::Id> arguments(info.args); |
|
|
|
vector<Id> arguments(info.args); |
|
|
|
for (int i = 0; i < info.args; ++i) |
|
|
|
arguments[i] = stackElement(m_stackHeight - i); |
|
|
|
if (_item.instruction() == Instruction::SSTORE) |
|
|
@ -130,6 +131,8 @@ void CommonSubexpressionEliminator::feedItem(AssemblyItem const& _item, bool _co |
|
|
|
storeInMemory(arguments[0], arguments[1]); |
|
|
|
else if (_item.instruction() == Instruction::MLOAD) |
|
|
|
setStackElement(m_stackHeight + _item.deposit(), loadFromMemory(arguments[0])); |
|
|
|
else if (_item.instruction() == Instruction::SHA3) |
|
|
|
setStackElement(m_stackHeight + _item.deposit(), applySha3(arguments.at(0), arguments.at(1))); |
|
|
|
else |
|
|
|
setStackElement(m_stackHeight + _item.deposit(), m_expressionClasses.find(_item, arguments, _copyItem)); |
|
|
|
} |
|
|
@ -142,7 +145,6 @@ 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); |
|
|
@ -163,7 +165,7 @@ void CommonSubexpressionEliminator::optimizeBreakingItem() |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
void CommonSubexpressionEliminator::setStackElement(int _stackHeight, ExpressionClasses::Id _class) |
|
|
|
void CommonSubexpressionEliminator::setStackElement(int _stackHeight, Id _class) |
|
|
|
{ |
|
|
|
m_stackElements[_stackHeight] = _class; |
|
|
|
} |
|
|
@ -194,26 +196,28 @@ ExpressionClasses::Id CommonSubexpressionEliminator::initialStackElement(int _st |
|
|
|
return m_expressionClasses.find(AssemblyItem(dupInstruction(1 - _stackHeight))); |
|
|
|
} |
|
|
|
|
|
|
|
void CommonSubexpressionEliminator::storeInStorage(ExpressionClasses::Id _slot, ExpressionClasses::Id _value) |
|
|
|
void CommonSubexpressionEliminator::storeInStorage(Id _slot, 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
|
|
|
|
// Copy over all values (i.e. retain knowledge about them) where we know that this store
|
|
|
|
// operation will not destroy the knowledge. Specifically, we copy storage locations we know
|
|
|
|
// are different from _slot or locations where we know that the stored value is equal to _value.
|
|
|
|
for (auto const& storageItem: m_storageContent) |
|
|
|
if (m_expressionClasses.knownToBeDifferent(storageItem.first, _slot)) |
|
|
|
if (m_expressionClasses.knownToBeDifferent(storageItem.first, _slot) || storageItem.second == _value) |
|
|
|
storageContents.insert(storageItem); |
|
|
|
m_storageContent = move(storageContents); |
|
|
|
ExpressionClasses::Id id = m_expressionClasses.find(Instruction::SSTORE, {_slot, _value}, true, m_sequenceNumber); |
|
|
|
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) |
|
|
|
ExpressionClasses::Id CommonSubexpressionEliminator::loadFromStorage(Id _slot) |
|
|
|
{ |
|
|
|
if (m_storageContent.count(_slot)) |
|
|
|
return m_storageContent.at(_slot); |
|
|
@ -221,7 +225,7 @@ ExpressionClasses::Id CommonSubexpressionEliminator::loadFromStorage(ExpressionC |
|
|
|
return m_storageContent[_slot] = m_expressionClasses.find(Instruction::SLOAD, {_slot}, true, m_sequenceNumber); |
|
|
|
} |
|
|
|
|
|
|
|
void CommonSubexpressionEliminator::storeInMemory(ExpressionClasses::Id _slot, ExpressionClasses::Id _value) |
|
|
|
void CommonSubexpressionEliminator::storeInMemory(Id _slot, 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
|
|
|
@ -233,14 +237,14 @@ void CommonSubexpressionEliminator::storeInMemory(ExpressionClasses::Id _slot, E |
|
|
|
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); |
|
|
|
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) |
|
|
|
ExpressionClasses::Id CommonSubexpressionEliminator::loadFromMemory(Id _slot) |
|
|
|
{ |
|
|
|
if (m_memoryContent.count(_slot)) |
|
|
|
return m_memoryContent.at(_slot); |
|
|
@ -248,6 +252,37 @@ ExpressionClasses::Id CommonSubexpressionEliminator::loadFromMemory(ExpressionCl |
|
|
|
return m_memoryContent[_slot] = m_expressionClasses.find(Instruction::MLOAD, {_slot}, true, m_sequenceNumber); |
|
|
|
} |
|
|
|
|
|
|
|
CommonSubexpressionEliminator::Id CommonSubexpressionEliminator::applySha3(Id _start, Id _length) |
|
|
|
{ |
|
|
|
// Special logic if length is a short constant, otherwise we cannot tell.
|
|
|
|
u256 const* l = m_expressionClasses.knownConstant(_length); |
|
|
|
// unknown or too large length
|
|
|
|
if (!l || *l > 128) |
|
|
|
return m_expressionClasses.find(Instruction::SHA3, {_start, _length}, true, m_sequenceNumber); |
|
|
|
|
|
|
|
vector<Id> arguments; |
|
|
|
for (u256 i = 0; i < *l; i += 32) |
|
|
|
{ |
|
|
|
Id slot = m_expressionClasses.find(Instruction::ADD, {_start, m_expressionClasses.find(i)}); |
|
|
|
arguments.push_back(loadFromMemory(slot)); |
|
|
|
} |
|
|
|
if (m_knownSha3Hashes.count(arguments)) |
|
|
|
return m_knownSha3Hashes.at(arguments); |
|
|
|
Id v; |
|
|
|
// If all arguments are known constants, compute the sha3 here
|
|
|
|
if (all_of(arguments.begin(), arguments.end(), [this](Id _a) { return !!m_expressionClasses.knownConstant(_a); })) |
|
|
|
{ |
|
|
|
bytes data; |
|
|
|
for (Id a: arguments) |
|
|
|
data += toBigEndian(*m_expressionClasses.knownConstant(a)); |
|
|
|
data.resize(size_t(*l)); |
|
|
|
v = m_expressionClasses.find(u256(sha3(data))); |
|
|
|
} |
|
|
|
else |
|
|
|
v = m_expressionClasses.find(Instruction::SHA3, {_start, _length}, true, m_sequenceNumber); |
|
|
|
return m_knownSha3Hashes[arguments] = v; |
|
|
|
} |
|
|
|
|
|
|
|
CSECodeGenerator::CSECodeGenerator( |
|
|
|
ExpressionClasses& _expressionClasses, |
|
|
|
vector<CSECodeGenerator::StoreOperation> const& _storeOperations |
|
|
@ -259,8 +294,8 @@ CSECodeGenerator::CSECodeGenerator( |
|
|
|
} |
|
|
|
|
|
|
|
AssemblyItems CSECodeGenerator::generateCode( |
|
|
|
map<int, ExpressionClasses::Id> const& _initialStack, |
|
|
|
map<int, ExpressionClasses::Id> const& _targetStackContents |
|
|
|
map<int, Id> const& _initialStack, |
|
|
|
map<int, Id> const& _targetStackContents |
|
|
|
) |
|
|
|
{ |
|
|
|
m_stack = _initialStack; |
|
|
@ -280,7 +315,7 @@ AssemblyItems CSECodeGenerator::generateCode( |
|
|
|
} |
|
|
|
|
|
|
|
// store all needed sequenced expressions
|
|
|
|
set<pair<unsigned, ExpressionClasses::Id>> sequencedExpressions; |
|
|
|
set<pair<unsigned, Id>> sequencedExpressions; |
|
|
|
for (auto const& p: m_neededBy) |
|
|
|
for (auto id: {p.first, p.second}) |
|
|
|
if (unsigned seqNr = m_expressionClasses.representative(id).sequenceNumber) |
|
|
@ -327,19 +362,20 @@ AssemblyItems CSECodeGenerator::generateCode( |
|
|
|
return m_generatedItems; |
|
|
|
} |
|
|
|
|
|
|
|
void CSECodeGenerator::addDependencies(ExpressionClasses::Id _c) |
|
|
|
void CSECodeGenerator::addDependencies(Id _c) |
|
|
|
{ |
|
|
|
if (m_neededBy.count(_c)) |
|
|
|
return; // we already computed the dependencies for _c
|
|
|
|
ExpressionClasses::Expression expr = m_expressionClasses.representative(_c); |
|
|
|
for (ExpressionClasses::Id argument: expr.arguments) |
|
|
|
for (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 |
|
|
|
expr.item->instruction() == Instruction::MLOAD || |
|
|
|
expr.item->instruction() == Instruction::SHA3 |
|
|
|
)) |
|
|
|
{ |
|
|
|
// this loads an unknown value from storage or memory and thus, in addition to its
|
|
|
@ -347,22 +383,52 @@ void CSECodeGenerator::addDependencies(ExpressionClasses::Id _c) |
|
|
|
// 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); |
|
|
|
Id slotToLoadFrom = expr.arguments.at(0); |
|
|
|
for (auto const& p: m_storeOperations) |
|
|
|
{ |
|
|
|
if (p.first.first != target) |
|
|
|
continue; |
|
|
|
ExpressionClasses::Id slot = p.first.second; |
|
|
|
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)) |
|
|
|
) |
|
|
|
bool knownToBeIndependent = false; |
|
|
|
switch (expr.item->instruction()) |
|
|
|
{ |
|
|
|
case Instruction::SLOAD: |
|
|
|
knownToBeIndependent = m_expressionClasses.knownToBeDifferent(slot, slotToLoadFrom); |
|
|
|
break; |
|
|
|
case Instruction::MLOAD: |
|
|
|
knownToBeIndependent = m_expressionClasses.knownToBeDifferentBy32(slot, slotToLoadFrom); |
|
|
|
break; |
|
|
|
case Instruction::SHA3: |
|
|
|
{ |
|
|
|
Id length = expr.arguments.at(1); |
|
|
|
Id offsetToStart = m_expressionClasses.find(Instruction::SUB, {slot, slotToLoadFrom}); |
|
|
|
u256 const* o = m_expressionClasses.knownConstant(offsetToStart); |
|
|
|
u256 const* l = m_expressionClasses.knownConstant(length); |
|
|
|
if (l && *l == 0) |
|
|
|
knownToBeIndependent = true; |
|
|
|
else if (o) |
|
|
|
{ |
|
|
|
// We could get problems here if both *o and *l are larger than 2**254
|
|
|
|
// but it is probably ok for the optimizer to produce wrong code for such cases
|
|
|
|
// which cannot be executed anyway because of the non-payable price.
|
|
|
|
if (u2s(*o) <= -32) |
|
|
|
knownToBeIndependent = true; |
|
|
|
else if (l && u2s(*o) >= 0 && *o >= *l) |
|
|
|
knownToBeIndependent = true; |
|
|
|
} |
|
|
|
break; |
|
|
|
} |
|
|
|
default: |
|
|
|
break; |
|
|
|
} |
|
|
|
if (knownToBeIndependent) |
|
|
|
continue; |
|
|
|
|
|
|
|
// note that store and load never have the same sequence number
|
|
|
|
ExpressionClasses::Id latestStore = storeOps.front().expression; |
|
|
|
Id latestStore = storeOps.front().expression; |
|
|
|
for (auto it = ++storeOps.begin(); it != storeOps.end(); ++it) |
|
|
|
if (it->sequenceNumber < expr.sequenceNumber) |
|
|
|
latestStore = it->expression; |
|
|
@ -372,7 +438,7 @@ void CSECodeGenerator::addDependencies(ExpressionClasses::Id _c) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
int CSECodeGenerator::generateClassElement(ExpressionClasses::Id _c, bool _allowSequenced) |
|
|
|
int CSECodeGenerator::generateClassElement(Id _c, bool _allowSequenced) |
|
|
|
{ |
|
|
|
// do some cleanup
|
|
|
|
removeStackTopIfPossible(); |
|
|
@ -392,8 +458,8 @@ int CSECodeGenerator::generateClassElement(ExpressionClasses::Id _c, bool _allow |
|
|
|
OptimizerException, |
|
|
|
"Sequence constrained operation requested out of sequence." |
|
|
|
); |
|
|
|
ExpressionClasses::Ids const& arguments = expr.arguments; |
|
|
|
for (ExpressionClasses::Id arg: boost::adaptors::reverse(arguments)) |
|
|
|
vector<Id> const& arguments = expr.arguments; |
|
|
|
for (Id arg: boost::adaptors::reverse(arguments)) |
|
|
|
generateClassElement(arg); |
|
|
|
|
|
|
|
// The arguments are somewhere on the stack now, so it remains to move them at the correct place.
|
|
|
@ -478,7 +544,7 @@ int CSECodeGenerator::generateClassElement(ExpressionClasses::Id _c, bool _allow |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
int CSECodeGenerator::classElementPosition(ExpressionClasses::Id _id) const |
|
|
|
int CSECodeGenerator::classElementPosition(Id _id) const |
|
|
|
{ |
|
|
|
assertThrow( |
|
|
|
m_classPositions.count(_id) && m_classPositions.at(_id) != c_invalidPosition, |
|
|
@ -488,7 +554,7 @@ int CSECodeGenerator::classElementPosition(ExpressionClasses::Id _id) const |
|
|
|
return m_classPositions.at(_id); |
|
|
|
} |
|
|
|
|
|
|
|
bool CSECodeGenerator::canBeRemoved(ExpressionClasses::Id _element, ExpressionClasses::Id _result) |
|
|
|
bool CSECodeGenerator::canBeRemoved(Id _element, Id _result) |
|
|
|
{ |
|
|
|
// Returns false if _element is finally needed or is needed by a class that has not been
|
|
|
|
// computed yet. Note that m_classPositions also includes classes that were deleted in the meantime.
|
|
|
@ -507,7 +573,7 @@ bool CSECodeGenerator::removeStackTopIfPossible() |
|
|
|
if (m_stack.empty()) |
|
|
|
return false; |
|
|
|
assertThrow(m_stack.count(m_stackHeight) > 0, OptimizerException, ""); |
|
|
|
ExpressionClasses::Id top = m_stack[m_stackHeight]; |
|
|
|
Id top = m_stack[m_stackHeight]; |
|
|
|
if (!canBeRemoved(top)) |
|
|
|
return false; |
|
|
|
m_generatedItems.push_back(AssemblyItem(Instruction::POP)); |
|
|
|