chriseth
10 years ago
5 changed files with 895 additions and 99 deletions
@ -0,0 +1,541 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum is free software: you can redistribute it and/or modify |
|||
it under the terms of the GNU General Public License as published by |
|||
the Free Software Foundation, either version 3 of the License, or |
|||
(at your option) any later version. |
|||
|
|||
cpp-ethereum is distributed in the hope that it will be useful, |
|||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
GNU General Public License for more details. |
|||
|
|||
You should have received a copy of the GNU General Public License |
|||
along with cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/**
|
|||
* @file CommonSubexpressionEliminator.cpp |
|||
* @author Christian <c@ethdev.com> |
|||
* @date 2015 |
|||
* Optimizer step for common subexpression elimination and stack reorganisation. |
|||
*/ |
|||
|
|||
#include <functional> |
|||
#include <boost/range/adaptor/reversed.hpp> |
|||
#include <libevmcore/CommonSubexpressionEliminator.h> |
|||
#include <libevmcore/Assembly.h> |
|||
|
|||
using namespace std; |
|||
using namespace dev; |
|||
using namespace dev::eth; |
|||
|
|||
vector<AssemblyItem> CommonSubexpressionEliminator::getOptimizedItems() |
|||
{ |
|||
map<int, EquivalenceClassId> initialStackContents; |
|||
map<int, EquivalenceClassId> targetStackContents; |
|||
int minHeight = m_stackHeight + 1; |
|||
if (!m_stackElements.empty()) |
|||
minHeight = min(minHeight, m_stackElements.begin()->first.first); |
|||
for (int height = minHeight; height <= max(0, m_stackHeight); ++height) |
|||
{ |
|||
// make sure it is created
|
|||
EquivalenceClassId c = getStackElement(height); |
|||
if (height <= 0) |
|||
initialStackContents[height] = getClass(AssemblyItem(dupInstruction(1 - height))); |
|||
if (height <= m_stackHeight) |
|||
targetStackContents[height] = c; |
|||
} |
|||
|
|||
// Debug info:
|
|||
//stream(cout, currentStackContents, targetStackContents);
|
|||
|
|||
return CSECodeGenerator().generateCode(initialStackContents, targetStackContents, m_equivalenceClasses); |
|||
} |
|||
|
|||
ostream& CommonSubexpressionEliminator::stream( |
|||
ostream& _out, |
|||
map<int, EquivalenceClassId> _currentStack, |
|||
map<int, EquivalenceClassId> _targetStack |
|||
) const |
|||
{ |
|||
auto streamEquivalenceClass = [this](ostream& _out, EquivalenceClassId _id) |
|||
{ |
|||
auto const& eqClass = m_equivalenceClasses.at(_id); |
|||
_out << " " << _id << ": " << *eqClass.first; |
|||
_out << "("; |
|||
for (EquivalenceClassId arg: eqClass.second) |
|||
_out << dec << arg << ","; |
|||
_out << ")" << endl; |
|||
}; |
|||
|
|||
_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.first << "(" << it.first.second << ") = "; |
|||
streamEquivalenceClass(_out, it.second); |
|||
} |
|||
_out << "Equivalence classes: " << endl; |
|||
for (EquivalenceClassId eqClass = 0; eqClass < m_equivalenceClasses.size(); ++eqClass) |
|||
streamEquivalenceClass(_out, eqClass); |
|||
|
|||
_out << "Current stack: " << endl; |
|||
for (auto const& it: _currentStack) |
|||
{ |
|||
_out << " " << dec << it.first << ": "; |
|||
streamEquivalenceClass(_out, it.second); |
|||
} |
|||
_out << "Target stack: " << endl; |
|||
for (auto const& it: _targetStack) |
|||
{ |
|||
_out << " " << dec << it.first << ": "; |
|||
streamEquivalenceClass(_out, it.second); |
|||
} |
|||
|
|||
return _out; |
|||
} |
|||
|
|||
void CommonSubexpressionEliminator::feedItem(AssemblyItem const& _item) |
|||
{ |
|||
if (_item.type() != Operation) |
|||
{ |
|||
if (_item.deposit() != 1) |
|||
BOOST_THROW_EXCEPTION(InvalidDeposit()); |
|||
setStackElement(++m_stackHeight, getClass(_item, {})); |
|||
} |
|||
else |
|||
{ |
|||
Instruction instruction = _item.instruction(); |
|||
InstructionInfo info = instructionInfo(instruction); |
|||
if (SemanticInformation::isDupInstruction(_item)) |
|||
setStackElement( |
|||
m_stackHeight + 1, |
|||
getStackElement(m_stackHeight - int(instruction) + int(Instruction::DUP1)) |
|||
); |
|||
else if (SemanticInformation::isSwapInstruction(_item)) |
|||
swapStackElements( |
|||
m_stackHeight, |
|||
m_stackHeight - 1 - int(instruction) + int(Instruction::SWAP1) |
|||
); |
|||
else if (instruction != Instruction::POP) |
|||
{ |
|||
vector<EquivalenceClassId> arguments(info.args); |
|||
for (int i = 0; i < info.args; ++i) |
|||
arguments[i] = getStackElement(m_stackHeight - i); |
|||
setStackElement(m_stackHeight + _item.deposit(), getClass(_item, arguments)); |
|||
} |
|||
m_stackHeight += _item.deposit(); |
|||
} |
|||
} |
|||
|
|||
void CommonSubexpressionEliminator::setStackElement(int _stackHeight, EquivalenceClassId _class) |
|||
{ |
|||
unsigned nextSequence = getNextStackElementSequence(_stackHeight); |
|||
m_stackElements[make_pair(_stackHeight, nextSequence)] = _class; |
|||
} |
|||
|
|||
void CommonSubexpressionEliminator::swapStackElements(int _stackHeightA, int _stackHeightB) |
|||
{ |
|||
if (_stackHeightA == _stackHeightB) |
|||
BOOST_THROW_EXCEPTION(OptimizerException() << errinfo_comment("Swap on same stack elements.")); |
|||
EquivalenceClassId classA = getStackElement(_stackHeightA); |
|||
EquivalenceClassId classB = getStackElement(_stackHeightB); |
|||
|
|||
unsigned nextSequenceA = getNextStackElementSequence(_stackHeightA); |
|||
unsigned nextSequenceB = getNextStackElementSequence(_stackHeightB); |
|||
m_stackElements[make_pair(_stackHeightA, nextSequenceA)] = classB; |
|||
m_stackElements[make_pair(_stackHeightB, nextSequenceB)] = classA; |
|||
} |
|||
|
|||
EquivalenceClassId CommonSubexpressionEliminator::getStackElement(int _stackHeight) |
|||
{ |
|||
// retrieve class by last sequence number
|
|||
unsigned nextSequence = getNextStackElementSequence(_stackHeight); |
|||
if (nextSequence > 0) |
|||
return m_stackElements[make_pair(_stackHeight, nextSequence - 1)]; |
|||
|
|||
// Stack element not found (not assigned yet), create new equivalence class.
|
|||
if (_stackHeight > 0) |
|||
BOOST_THROW_EXCEPTION(OptimizerException() << errinfo_comment("Stack element accessed before assignment.")); |
|||
if (_stackHeight <= -16) |
|||
BOOST_THROW_EXCEPTION(OptimizerException() << errinfo_comment("Stack too deep.")); |
|||
// This is a special assembly item that refers to elements pre-existing on the initial stack.
|
|||
m_spareAssemblyItem.push_back(make_shared<AssemblyItem>(dupInstruction(1 - _stackHeight))); |
|||
m_equivalenceClasses.push_back(make_pair(m_spareAssemblyItem.back().get(), EquivalenceClassIds())); |
|||
return m_stackElements[make_pair(_stackHeight, nextSequence)] = EquivalenceClassId(m_equivalenceClasses.size() - 1); |
|||
} |
|||
|
|||
EquivalenceClassId CommonSubexpressionEliminator::getClass( |
|||
const AssemblyItem& _item, |
|||
EquivalenceClassIds const& _arguments |
|||
) |
|||
{ |
|||
// TODO: do a clever search, i.e.
|
|||
// - check for the presence of constants in the argument classes and do arithmetic
|
|||
// - check whether the two items are equal for a SUB instruction
|
|||
// - check whether 0 or 1 is in one of the classes for a MUL
|
|||
|
|||
EquivalenceClassIds args = _arguments; |
|||
if (SemanticInformation::isCommutativeOperation(_item)) |
|||
sort(args.begin(), args.end()); |
|||
|
|||
//@todo use a better data structure for search here
|
|||
for (EquivalenceClassId c = 0; c < m_equivalenceClasses.size(); ++c) |
|||
{ |
|||
AssemblyItem const& classItem = *m_equivalenceClasses.at(c).first; |
|||
if (classItem != _item) |
|||
continue; |
|||
|
|||
assertThrow( |
|||
args.size() == m_equivalenceClasses.at(c).second.size(), |
|||
OptimizerException, |
|||
"Equal assembly items with different number of arguments." |
|||
); |
|||
if (equal(args.begin(), args.end(), m_equivalenceClasses.at(c).second.begin())) |
|||
return c; |
|||
} |
|||
// constant folding
|
|||
if (_item.type() == Operation && args.size() == 2 && all_of( |
|||
args.begin(), |
|||
args.end(), |
|||
[this](EquivalenceClassId eqc) { return m_equivalenceClasses.at(eqc).first->match(Push); })) |
|||
{ |
|||
auto signextend = [](u256 const& _a, u256 const& _b) -> u256 |
|||
{ |
|||
if (_a >= 31) |
|||
return _b; |
|||
unsigned testBit = unsigned(_a) * 8 + 7; |
|||
u256 mask = (u256(1) << testBit) - 1; |
|||
return boost::multiprecision::bit_test(_b, testBit) ? _b | ~mask : _b & mask; |
|||
}; |
|||
map<Instruction, function<u256(u256 const&, u256 const&)>> const arithmetics = |
|||
{ |
|||
{ Instruction::SUB, [](u256 const& _a, u256 const& _b) -> u256 {return _a - _b; } }, |
|||
{ Instruction::DIV, [](u256 const& _a, u256 const& _b) -> u256 {return _b == 0 ? 0 : _a / _b; } }, |
|||
{ Instruction::SDIV, [](u256 const& _a, u256 const& _b) -> u256 { return _b == 0 ? 0 : s2u(u2s(_a) / u2s(_b)); } }, |
|||
{ Instruction::MOD, [](u256 const& _a, u256 const& _b) -> u256 { return _b == 0 ? 0 : _a % _b; } }, |
|||
{ Instruction::SMOD, [](u256 const& _a, u256 const& _b) -> u256 { return _b == 0 ? 0 : s2u(u2s(_a) % u2s(_b)); } }, |
|||
{ Instruction::EXP, [](u256 const& _a, u256 const& _b) -> u256 { return (u256)boost::multiprecision::powm(bigint(_a), bigint(_b), bigint(1) << 256); } }, |
|||
{ Instruction::SIGNEXTEND, signextend }, |
|||
{ Instruction::LT, [](u256 const& _a, u256 const& _b) -> u256 { return _a < _b ? 1 : 0; } }, |
|||
{ Instruction::GT, [](u256 const& _a, u256 const& _b) -> u256 { return _a > _b ? 1 : 0; } }, |
|||
{ Instruction::SLT, [](u256 const& _a, u256 const& _b) -> u256 { return u2s(_a) < u2s(_b) ? 1 : 0; } }, |
|||
{ Instruction::SGT, [](u256 const& _a, u256 const& _b) -> u256 { return u2s(_a) > u2s(_b) ? 1 : 0; } }, |
|||
{ Instruction::EQ, [](u256 const& _a, u256 const& _b) -> u256 { return _a == _b ? 1 : 0; } }, |
|||
{ Instruction::ADD, [](u256 const& _a, u256 const& _b) -> u256 { return _a + _b; } }, |
|||
{ Instruction::MUL, [](u256 const& _a, u256 const& _b) -> u256 { return _a * _b; } }, |
|||
{ Instruction::AND, [](u256 const& _a, u256 const& _b) -> u256 { return _a & _b; } }, |
|||
{ Instruction::OR, [](u256 const& _a, u256 const& _b) -> u256 { return _a | _b; } }, |
|||
{ Instruction::XOR, [](u256 const& _a, u256 const& _b) -> u256 { return _a ^ _b; } }, |
|||
}; |
|||
if (arithmetics.count(_item.instruction())) |
|||
{ |
|||
u256 result = arithmetics.at(_item.instruction())( |
|||
m_equivalenceClasses.at(args[0]).first->data(), |
|||
m_equivalenceClasses.at(args[1]).first->data() |
|||
); |
|||
m_spareAssemblyItem.push_back(make_shared<AssemblyItem>(result)); |
|||
return getClass(*m_spareAssemblyItem.back()); |
|||
} |
|||
} |
|||
m_equivalenceClasses.push_back(make_pair(&_item, args)); |
|||
return m_equivalenceClasses.size() - 1; |
|||
} |
|||
|
|||
unsigned CommonSubexpressionEliminator::getNextStackElementSequence(int _stackHeight) |
|||
{ |
|||
auto it = m_stackElements.upper_bound(make_pair(_stackHeight, unsigned(-1))); |
|||
if (it == m_stackElements.begin()) |
|||
return 0; |
|||
--it; |
|||
if (it->first.first == _stackHeight) |
|||
return it->first.second + 1; |
|||
else |
|||
return 0; |
|||
} |
|||
|
|||
bool SemanticInformation::breaksBasicBlock(AssemblyItem const& _item) |
|||
{ |
|||
switch (_item.type()) |
|||
{ |
|||
default: |
|||
case UndefinedItem: |
|||
case Tag: |
|||
return true; |
|||
case Push: |
|||
case PushString: |
|||
case PushTag: |
|||
case PushSub: |
|||
case PushSubSize: |
|||
case PushProgramSize: |
|||
case PushData: |
|||
return false; |
|||
case Operation: |
|||
{ |
|||
if (isSwapInstruction(_item) || isDupInstruction(_item)) |
|||
return false; |
|||
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()); |
|||
// the second requirement will be lifted once it is implemented
|
|||
return info.sideEffects || info.args > 2; |
|||
} |
|||
} |
|||
} |
|||
|
|||
bool SemanticInformation::isCommutativeOperation(AssemblyItem const& _item) |
|||
{ |
|||
if (_item.type() != Operation) |
|||
return false; |
|||
switch (_item.instruction()) |
|||
{ |
|||
case Instruction::ADD: |
|||
case Instruction::MUL: |
|||
case Instruction::EQ: |
|||
case Instruction::AND: |
|||
case Instruction::OR: |
|||
case Instruction::XOR: |
|||
return true; |
|||
default: |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
bool SemanticInformation::isDupInstruction(AssemblyItem const& _item) |
|||
{ |
|||
if (_item.type() != Operation) |
|||
return false; |
|||
return Instruction::DUP1 <= _item.instruction() && _item.instruction() <= Instruction::DUP16; |
|||
} |
|||
|
|||
bool SemanticInformation::isSwapInstruction(AssemblyItem const& _item) |
|||
{ |
|||
if (_item.type() != Operation) |
|||
return false; |
|||
return Instruction::SWAP1 <= _item.instruction() && _item.instruction() <= Instruction::SWAP16; |
|||
} |
|||
|
|||
AssemblyItems CSECodeGenerator::generateCode( |
|||
map<int, EquivalenceClassId> const& _initialStack, |
|||
map<int, EquivalenceClassId> const& _targetStackContents, |
|||
vector<pair<AssemblyItem const*, EquivalenceClassIds>> const& _equivalenceClasses |
|||
) |
|||
{ |
|||
// reset
|
|||
*this = move(CSECodeGenerator()); |
|||
m_stack = _initialStack; |
|||
m_equivalenceClasses = _equivalenceClasses; |
|||
for (auto const& item: m_stack) |
|||
if (!m_classPositions.count(item.second)) |
|||
m_classPositions[item.second] = item.first; |
|||
|
|||
// @todo: provide information about the positions of copies of class elements
|
|||
|
|||
// generate the dependency graph
|
|||
for (auto const& targetItem: _targetStackContents) |
|||
{ |
|||
m_finalClasses.insert(targetItem.second); |
|||
addDependencies(targetItem.second); |
|||
} |
|||
|
|||
// generate the actual elements
|
|||
for (auto const& targetItem: _targetStackContents) |
|||
{ |
|||
removeStackTopIfPossible(); |
|||
int position = generateClassElement(targetItem.second); |
|||
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); |
|||
} |
|||
|
|||
// remove surplus elements
|
|||
while (removeStackTopIfPossible()) |
|||
{ |
|||
// no-op
|
|||
} |
|||
|
|||
// check validity
|
|||
int finalHeight = 0; |
|||
if (!_targetStackContents.empty()) |
|||
// have target stack, so its height should be the final height
|
|||
finalHeight = (--_targetStackContents.end())->first; |
|||
else if (!_initialStack.empty()) |
|||
// no target stack, only erase the initial stack
|
|||
finalHeight = _initialStack.begin()->first - 1; |
|||
else |
|||
// 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(EquivalenceClassId _c) |
|||
{ |
|||
if (m_neededBy.count(_c)) |
|||
return; |
|||
for (EquivalenceClassId argument: m_equivalenceClasses.at(_c).second) |
|||
{ |
|||
addDependencies(argument); |
|||
m_neededBy.insert(make_pair(argument, _c)); |
|||
} |
|||
} |
|||
|
|||
int CSECodeGenerator::generateClassElement(EquivalenceClassId _c) |
|||
{ |
|||
if (m_classPositions.count(_c)) |
|||
{ |
|||
assertThrow( |
|||
m_classPositions[_c] != c_invalidPosition, |
|||
OptimizerException, |
|||
"Element already removed but still needed." |
|||
); |
|||
return m_classPositions[_c]; |
|||
} |
|||
EquivalenceClassIds const& arguments = m_equivalenceClasses.at(_c).second; |
|||
for (EquivalenceClassId 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.
|
|||
// This is quite difficult as sometimes, the values also have to removed in this process
|
|||
// (if canBeRemoved() returns true) and the two arguments can be equal. For now, this is
|
|||
// implemented for every single case for combinations of up to two arguments manually.
|
|||
if (arguments.size() == 1) |
|||
{ |
|||
if (canBeRemoved(arguments[0], _c)) |
|||
appendSwapOrRemove(generateClassElement(arguments[0])); |
|||
else |
|||
appendDup(generateClassElement(arguments[0])); |
|||
} |
|||
else if (arguments.size() == 2) |
|||
{ |
|||
if (canBeRemoved(arguments[1], _c)) |
|||
{ |
|||
appendSwapOrRemove(generateClassElement(arguments[1])); |
|||
if (arguments[0] == arguments[1]) |
|||
appendDup(m_stackHeight); |
|||
else if (canBeRemoved(arguments[0], _c)) |
|||
{ |
|||
appendSwapOrRemove(m_stackHeight - 1); |
|||
appendSwapOrRemove(generateClassElement(arguments[0])); |
|||
} |
|||
else |
|||
appendDup(generateClassElement(arguments[0])); |
|||
} |
|||
else |
|||
{ |
|||
if (arguments[0] == arguments[1]) |
|||
{ |
|||
appendDup(generateClassElement(arguments[0])); |
|||
appendDup(m_stackHeight); |
|||
} |
|||
else if (canBeRemoved(arguments[0], _c)) |
|||
{ |
|||
appendSwapOrRemove(generateClassElement(arguments[0])); |
|||
appendDup(generateClassElement(arguments[1])); |
|||
appendSwapOrRemove(m_stackHeight - 1); |
|||
} |
|||
else |
|||
{ |
|||
appendDup(generateClassElement(arguments[1])); |
|||
appendDup(generateClassElement(arguments[0])); |
|||
} |
|||
} |
|||
} |
|||
else |
|||
assertThrow( |
|||
arguments.size() <= 2, |
|||
OptimizerException, |
|||
"Opcodes with more than two arguments not implemented yet." |
|||
); |
|||
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_equivalenceClasses.at(_c).first; |
|||
while (SemanticInformation::isCommutativeOperation(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); |
|||
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_equivalenceClasses.at(_c).first); |
|||
m_stack[m_stackHeight] = _c; |
|||
return m_classPositions[_c] = m_stackHeight; |
|||
} |
|||
|
|||
bool CSECodeGenerator::canBeRemoved(EquivalenceClassId _element, EquivalenceClassId _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.
|
|||
if (m_finalClasses.count(_element)) |
|||
return false; |
|||
|
|||
auto range = m_neededBy.equal_range(_element); |
|||
for (auto it = range.first; it != range.second; ++it) |
|||
if (it->second != _result && !m_classPositions.count(it->second)) |
|||
return false; |
|||
return true; |
|||
} |
|||
|
|||
bool CSECodeGenerator::removeStackTopIfPossible() |
|||
{ |
|||
if (m_stack.empty()) |
|||
return false; |
|||
assertThrow(m_stack.count(m_stackHeight), OptimizerException, ""); |
|||
EquivalenceClassId top = m_stack[m_stackHeight]; |
|||
if (!canBeRemoved(top)) |
|||
return false; |
|||
m_generatedItems.push_back(AssemblyItem(Instruction::POP)); |
|||
m_stack.erase(m_stackHeight); |
|||
m_stackHeight--; |
|||
return true; |
|||
} |
|||
|
|||
void CSECodeGenerator::appendDup(int _fromPosition) |
|||
{ |
|||
int nr = 1 + m_stackHeight - _fromPosition; |
|||
assertThrow(1 <= nr && nr <= 16, OptimizerException, "Stack too deep."); |
|||
m_generatedItems.push_back(AssemblyItem(dupInstruction(nr))); |
|||
m_stackHeight++; |
|||
m_stack[m_stackHeight] = m_stack[_fromPosition]; |
|||
} |
|||
|
|||
void CSECodeGenerator::appendSwapOrRemove(int _fromPosition) |
|||
{ |
|||
if (_fromPosition == m_stackHeight) |
|||
return; |
|||
int nr = m_stackHeight - _fromPosition; |
|||
assertThrow(1 <= nr && nr <= 16, OptimizerException, "Stack too deep."); |
|||
m_generatedItems.push_back(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) |
|||
m_classPositions[m_stack[m_stackHeight]] = _fromPosition; |
|||
if (m_classPositions[m_stack[_fromPosition]] == _fromPosition) |
|||
m_classPositions[m_stack[_fromPosition]] = m_stackHeight; |
|||
swap(m_stack[m_stackHeight], m_stack[_fromPosition]); |
|||
if (m_generatedItems.size() >= 2 && |
|||
SemanticInformation::isSwapInstruction(m_generatedItems.back()) && |
|||
*(m_generatedItems.end() - 2) == m_generatedItems.back()) |
|||
{ |
|||
m_generatedItems.pop_back(); |
|||
m_generatedItems.pop_back(); |
|||
} |
|||
} |
|||
|
|||
void CSECodeGenerator::appendItem(AssemblyItem const& _item) |
|||
{ |
|||
m_generatedItems.push_back(_item); |
|||
m_stackHeight += _item.deposit(); |
|||
} |
@ -0,0 +1,187 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum is free software: you can redistribute it and/or modify |
|||
it under the terms of the GNU General Public License as published by |
|||
the Free Software Foundation, either version 3 of the License, or |
|||
(at your option) any later version. |
|||
|
|||
cpp-ethereum is distributed in the hope that it will be useful, |
|||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
GNU General Public License for more details. |
|||
|
|||
You should have received a copy of the GNU General Public License |
|||
along with cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/**
|
|||
* @file CommonSubexpressionEliminator.h |
|||
* @author Christian <c@ethdev.com> |
|||
* @date 2015 |
|||
* Optimizer step for common subexpression elimination and stack reorganisation. |
|||
*/ |
|||
|
|||
#pragma once |
|||
|
|||
#include <vector> |
|||
#include <map> |
|||
#include <ostream> |
|||
#include <libdevcore/CommonIO.h> |
|||
#include <libdevcore/Exceptions.h> |
|||
|
|||
namespace dev |
|||
{ |
|||
namespace eth |
|||
{ |
|||
|
|||
class AssemblyItem; |
|||
using AssemblyItems = std::vector<AssemblyItem>; |
|||
|
|||
using EquivalenceClassId = unsigned; |
|||
using EquivalenceClassIds = std::vector<EquivalenceClassId>; |
|||
|
|||
/**
|
|||
* Optimizer step that performs common subexpression elimination and stack reorganisation, |
|||
* i.e. it tries to infer equality among expressions and compute the values of two expressions |
|||
* 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. |
|||
* |
|||
* 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. |
|||
*/ |
|||
class CommonSubexpressionEliminator |
|||
{ |
|||
public: |
|||
/// 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> |
|||
_AssemblyItemIterator feedItems(_AssemblyItemIterator _iterator, _AssemblyItemIterator _end); |
|||
|
|||
/// @returns the resulting items after optimization.
|
|||
AssemblyItems getOptimizedItems(); |
|||
|
|||
/// Streams debugging information to @a _out.
|
|||
std::ostream& stream( |
|||
std::ostream& _out, |
|||
std::map<int, EquivalenceClassId> _currentStack = std::map<int, EquivalenceClassId>(), |
|||
std::map<int, EquivalenceClassId> _targetStack = std::map<int, EquivalenceClassId>() |
|||
) const; |
|||
|
|||
private: |
|||
/// Feeds the item into the system for analysis.
|
|||
void feedItem(AssemblyItem const& _item); |
|||
|
|||
/// Assigns a new equivalence class to the next sequence number of the given stack element.
|
|||
void setStackElement(int _stackHeight, EquivalenceClassId _class); |
|||
/// Swaps the given stack elements in their next sequence number.
|
|||
void swapStackElements(int _stackHeightA, int _stackHeightB); |
|||
/// Retrieves the current equivalence class fo the given stack element (or generates a new
|
|||
/// one if it does not exist yet).
|
|||
EquivalenceClassId getStackElement(int _stackHeight); |
|||
/// Retrieves the equivalence class resulting from the given item applied to the given classes,
|
|||
/// might also create a new one.
|
|||
EquivalenceClassId getClass(AssemblyItem const& _item, EquivalenceClassIds const& _arguments = {}); |
|||
|
|||
/// @returns the next sequence number of the given stack element.
|
|||
unsigned getNextStackElementSequence(int _stackHeight); |
|||
|
|||
/// Current stack height, can be negative.
|
|||
int m_stackHeight = 0; |
|||
/// Mapping (stack height, sequence number) -> equivalence class
|
|||
std::map<std::pair<int, unsigned>, EquivalenceClassId> m_stackElements; |
|||
/// Vector of equivalence class representatives - we only store one item of an equivalence
|
|||
/// class and the index is used as identifier.
|
|||
std::vector<std::pair<AssemblyItem const*, EquivalenceClassIds>> m_equivalenceClasses; |
|||
/// List of items generated during analysis.
|
|||
std::vector<std::shared_ptr<AssemblyItem>> m_spareAssemblyItem; |
|||
}; |
|||
|
|||
/**
|
|||
* Helper functions to provide context-independent information about assembly items. |
|||
*/ |
|||
struct SemanticInformation |
|||
{ |
|||
/// @returns true if the given items starts a new basic block
|
|||
static bool breaksBasicBlock(AssemblyItem const& _item); |
|||
/// @returns true if the item is a two-argument operation whose value does not depend on the
|
|||
/// order of its arguments.
|
|||
static bool isCommutativeOperation(AssemblyItem const& _item); |
|||
static bool isDupInstruction(AssemblyItem const& _item); |
|||
static bool isSwapInstruction(AssemblyItem const& _item); |
|||
}; |
|||
|
|||
/**
|
|||
* Unit that generates code from current stack layout, target stack layout and information about |
|||
* the equivalence classes. |
|||
*/ |
|||
class CSECodeGenerator |
|||
{ |
|||
public: |
|||
/// @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 resuts the state of the object for each call.
|
|||
AssemblyItems generateCode( |
|||
std::map<int, EquivalenceClassId> const& _initialStack, |
|||
std::map<int, EquivalenceClassId> const& _targetStackContents, |
|||
std::vector<std::pair<AssemblyItem const*, EquivalenceClassIds>> const& _equivalenceClasses |
|||
); |
|||
|
|||
private: |
|||
/// Recursively discovers all dependencies to @a m_requests.
|
|||
void addDependencies(EquivalenceClassId _c); |
|||
|
|||
/// Produce code that generates the given element if it is not yet present.
|
|||
/// @returns the stack position of the element.
|
|||
int generateClassElement(EquivalenceClassId _c); |
|||
|
|||
/// @returns true if @a _element can be removed - in general or, if given, while computing @a _result.
|
|||
bool canBeRemoved(EquivalenceClassId _element, EquivalenceClassId _result = EquivalenceClassId(-1)); |
|||
|
|||
/// Appends code to remove the topmost stack element if it can be removed.
|
|||
bool removeStackTopIfPossible(); |
|||
|
|||
/// Appends a dup instruction to m_generatedItems to retrieve the element at the given stack position.
|
|||
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); |
|||
/// Appends the given assembly item.
|
|||
void appendItem(AssemblyItem const& _item); |
|||
|
|||
static const int c_invalidPosition = -0x7fffffff; |
|||
|
|||
AssemblyItems m_generatedItems; |
|||
/// Current height of the stack relative to the start.
|
|||
int m_stackHeight = 0; |
|||
/// If (b, a) is in m_requests then b is needed to compute a.
|
|||
std::multimap<EquivalenceClassId, EquivalenceClassId> m_neededBy; |
|||
/// Current content of the stack.
|
|||
std::map<int, EquivalenceClassId> m_stack; |
|||
/// Current positions of equivalence classes, equal to c_invalidPosition if already deleted.
|
|||
std::map<EquivalenceClassId, int> m_classPositions; |
|||
|
|||
/// The actual eqivalence class items and how to compute them.
|
|||
std::vector<std::pair<AssemblyItem const*, EquivalenceClassIds>> m_equivalenceClasses; |
|||
/// The set of equivalence classes that should be present on the stack at the end.
|
|||
std::set<EquivalenceClassId> m_finalClasses; |
|||
}; |
|||
|
|||
template <class _AssemblyItemIterator> |
|||
_AssemblyItemIterator CommonSubexpressionEliminator::feedItems( |
|||
_AssemblyItemIterator _iterator, |
|||
_AssemblyItemIterator _end |
|||
) |
|||
{ |
|||
for (; _iterator != _end && !SemanticInformation::breaksBasicBlock(*_iterator); ++_iterator) |
|||
feedItem(*_iterator); |
|||
return _iterator; |
|||
} |
|||
|
|||
} |
|||
} |
Loading…
Reference in new issue