mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Reuse state during common subexpression elimination.
This commit is contained in:
parent
6cc71a188f
commit
9d3f0de31b
50
Assembly.cpp
50
Assembly.cpp
@ -311,39 +311,26 @@ Assembly& Assembly::optimise(bool _enable)
|
|||||||
copt << toString(*this);
|
copt << toString(*this);
|
||||||
count = 0;
|
count = 0;
|
||||||
|
|
||||||
//@todo CFG interface should be a generator, that returns an item and a pointer to a
|
copt << "Performing optimisation...";
|
||||||
// knownstate, which has to replace the current state if it is not null.
|
|
||||||
// Feed these items to the CSE, but also store them and replace the stored version
|
|
||||||
// if the items generated by the CSE are shorter. (or even use less gas?)
|
|
||||||
copt << "Performing control flow analysis...";
|
|
||||||
{
|
{
|
||||||
ControlFlowGraph cfg(m_items);
|
ControlFlowGraph cfg(m_items);
|
||||||
AssemblyItems optItems;
|
AssemblyItems optimisedItems;
|
||||||
for (BasicBlock const& block: cfg.optimisedBlocks())
|
for (BasicBlock const& block: cfg.optimisedBlocks())
|
||||||
copy(m_items.begin() + block.begin, m_items.begin() + block.end,
|
|
||||||
back_inserter(optItems));
|
|
||||||
if (optItems.size() < m_items.size())
|
|
||||||
{
|
{
|
||||||
copt << "Old size: " << m_items.size() << ", new size: " << optItems.size();
|
assertThrow(!!block.startState, OptimizerException, "");
|
||||||
m_items = move(optItems);
|
CommonSubexpressionEliminator eliminator(*block.startState);
|
||||||
count++;
|
auto iter = m_items.begin() + block.begin;
|
||||||
}
|
auto const end = m_items.begin() + block.end;
|
||||||
}
|
while (iter < end)
|
||||||
|
|
||||||
copt << "Performing common subexpression elimination...";
|
|
||||||
for (auto iter = m_items.begin(); iter != m_items.end();)
|
|
||||||
{
|
{
|
||||||
//@todo use only a single state / expression classes instance.
|
|
||||||
KnownState state(make_shared<ExpressionClasses>());
|
|
||||||
CommonSubexpressionEliminator eliminator(state);
|
|
||||||
auto orig = iter;
|
auto orig = iter;
|
||||||
iter = eliminator.feedItems(iter, m_items.end());
|
iter = eliminator.feedItems(iter, end);
|
||||||
AssemblyItems optItems;
|
|
||||||
bool shouldReplace = false;
|
bool shouldReplace = false;
|
||||||
|
AssemblyItems optimisedChunk;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
optItems = eliminator.getOptimizedItems();
|
optimisedChunk = eliminator.getOptimizedItems();
|
||||||
shouldReplace = (optItems.size() < size_t(iter - orig));
|
shouldReplace = (optimisedChunk.size() < size_t(iter - orig));
|
||||||
}
|
}
|
||||||
catch (StackTooDeepException const&)
|
catch (StackTooDeepException const&)
|
||||||
{
|
{
|
||||||
@ -353,12 +340,16 @@ Assembly& Assembly::optimise(bool _enable)
|
|||||||
|
|
||||||
if (shouldReplace)
|
if (shouldReplace)
|
||||||
{
|
{
|
||||||
copt << "Old size: " << (iter - orig) << ", new size: " << optItems.size();
|
copt << "Old size: " << (iter - orig) << ", new size: " << optimisedChunk.size();
|
||||||
count++;
|
count++;
|
||||||
for (auto moveIter = optItems.begin(); moveIter != optItems.end(); ++orig, ++moveIter)
|
optimisedItems += optimisedChunk;
|
||||||
*orig = move(*moveIter);
|
|
||||||
iter = m_items.erase(orig, iter);
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
copy(orig, iter, back_inserter(optimisedItems));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (optimisedItems.size() < m_items.size())
|
||||||
|
m_items = move(optimisedItems);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -461,7 +452,8 @@ bytes Assembly::assemble() const
|
|||||||
for (auto const& i: tagRef)
|
for (auto const& i: tagRef)
|
||||||
{
|
{
|
||||||
bytesRef r(ret.data() + i.first, bytesPerTag);
|
bytesRef r(ret.data() + i.first, bytesPerTag);
|
||||||
toBigEndian(tagPos[i.second], r);
|
//@todo in the failure case, we could use the position of the invalid jumpdest
|
||||||
|
toBigEndian(i.second < tagPos.size() ? tagPos[i.second] : (1 << (8 * bytesPerTag)) - 1, r);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!m_data.empty())
|
if (!m_data.empty())
|
||||||
|
@ -45,16 +45,22 @@ vector<AssemblyItem> CommonSubexpressionEliminator::getOptimizedItems()
|
|||||||
for (int height = minHeight; height <= m_state.stackHeight(); ++height)
|
for (int height = minHeight; height <= m_state.stackHeight(); ++height)
|
||||||
targetStackContents[height] = m_state.stackElement(height, SourceLocation());
|
targetStackContents[height] = m_state.stackElement(height, SourceLocation());
|
||||||
|
|
||||||
// Debug info:
|
|
||||||
//stream(cout, initialStackContents, targetStackContents);
|
|
||||||
|
|
||||||
AssemblyItems items = CSECodeGenerator(m_state.expressionClasses(), m_storeOperations).generateCode(
|
AssemblyItems items = CSECodeGenerator(m_state.expressionClasses(), m_storeOperations).generateCode(
|
||||||
m_initialState.stackHeight(),
|
m_initialState.stackHeight(),
|
||||||
initialStackContents,
|
initialStackContents,
|
||||||
targetStackContents
|
targetStackContents
|
||||||
);
|
);
|
||||||
if (m_breakingItem)
|
if (m_breakingItem)
|
||||||
|
{
|
||||||
items.push_back(*m_breakingItem);
|
items.push_back(*m_breakingItem);
|
||||||
|
m_state.feedItem(*m_breakingItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
// cleanup
|
||||||
|
m_initialState = m_state;
|
||||||
|
m_breakingItem = nullptr;
|
||||||
|
m_storeOperations.clear();
|
||||||
|
|
||||||
return items;
|
return items;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -113,6 +119,7 @@ AssemblyItems CSECodeGenerator::generateCode(
|
|||||||
{
|
{
|
||||||
m_stackHeight = _initialStackHeight;
|
m_stackHeight = _initialStackHeight;
|
||||||
m_stack = _initialStack;
|
m_stack = _initialStack;
|
||||||
|
m_targetStack = _targetStackContents;
|
||||||
for (auto const& item: m_stack)
|
for (auto const& item: m_stack)
|
||||||
if (!m_classPositions.count(item.second))
|
if (!m_classPositions.count(item.second))
|
||||||
m_classPositions[item.second] = item.first;
|
m_classPositions[item.second] = item.first;
|
||||||
@ -122,7 +129,7 @@ AssemblyItems CSECodeGenerator::generateCode(
|
|||||||
// generate the dependency graph starting from final storage and memory writes and target stack contents
|
// generate the dependency graph starting from final storage and memory writes and target stack contents
|
||||||
for (auto const& p: m_storeOperations)
|
for (auto const& p: m_storeOperations)
|
||||||
addDependencies(p.second.back().expression);
|
addDependencies(p.second.back().expression);
|
||||||
for (auto const& targetItem: _targetStackContents)
|
for (auto const& targetItem: m_targetStack)
|
||||||
{
|
{
|
||||||
m_finalClasses.insert(targetItem.second);
|
m_finalClasses.insert(targetItem.second);
|
||||||
addDependencies(targetItem.second);
|
addDependencies(targetItem.second);
|
||||||
@ -141,8 +148,10 @@ AssemblyItems CSECodeGenerator::generateCode(
|
|||||||
generateClassElement(seqAndId.second, true);
|
generateClassElement(seqAndId.second, true);
|
||||||
|
|
||||||
// generate the target stack elements
|
// generate the target stack elements
|
||||||
for (auto const& targetItem: _targetStackContents)
|
for (auto const& targetItem: m_targetStack)
|
||||||
{
|
{
|
||||||
|
if (m_stack.count(targetItem.first) && m_stack.at(targetItem.first) == targetItem.second)
|
||||||
|
continue; // already there
|
||||||
int position = generateClassElement(targetItem.second);
|
int position = generateClassElement(targetItem.second);
|
||||||
assertThrow(position != c_invalidPosition, OptimizerException, "");
|
assertThrow(position != c_invalidPosition, OptimizerException, "");
|
||||||
if (position == targetItem.first)
|
if (position == targetItem.first)
|
||||||
@ -164,21 +173,24 @@ AssemblyItems CSECodeGenerator::generateCode(
|
|||||||
|
|
||||||
// check validity
|
// check validity
|
||||||
int finalHeight = 0;
|
int finalHeight = 0;
|
||||||
if (!_targetStackContents.empty())
|
if (!m_targetStack.empty())
|
||||||
// have target stack, so its height should be the final height
|
// have target stack, so its height should be the final height
|
||||||
finalHeight = (--_targetStackContents.end())->first;
|
finalHeight = (--m_targetStack.end())->first;
|
||||||
else if (!_initialStack.empty())
|
else if (!_initialStack.empty())
|
||||||
// no target stack, only erase the initial stack
|
// no target stack, only erase the initial stack
|
||||||
finalHeight = _initialStack.begin()->first - 1;
|
finalHeight = _initialStack.begin()->first - 1;
|
||||||
else
|
else
|
||||||
// neither initial no target stack, no change in height
|
// neither initial no target stack, no change in height
|
||||||
finalHeight = 0;
|
finalHeight = _initialStackHeight;
|
||||||
assertThrow(finalHeight == m_stackHeight, OptimizerException, "Incorrect final stack height.");
|
assertThrow(finalHeight == m_stackHeight, OptimizerException, "Incorrect final stack height.");
|
||||||
|
|
||||||
return m_generatedItems;
|
return m_generatedItems;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CSECodeGenerator::addDependencies(Id _c)
|
void CSECodeGenerator::addDependencies(Id _c)
|
||||||
{
|
{
|
||||||
|
if (m_classPositions.count(_c))
|
||||||
|
return; // it is already on the stack
|
||||||
if (m_neededBy.count(_c))
|
if (m_neededBy.count(_c))
|
||||||
return; // we already computed the dependencies for _c
|
return; // we already computed the dependencies for _c
|
||||||
ExpressionClasses::Expression expr = m_expressionClasses.representative(_c);
|
ExpressionClasses::Expression expr = m_expressionClasses.representative(_c);
|
||||||
@ -340,7 +352,7 @@ int CSECodeGenerator::generateClassElement(Id _c, bool _allowSequenced)
|
|||||||
// this will not append a swap but remove the one that is already there
|
// this will not append a swap but remove the one that is already there
|
||||||
appendOrRemoveSwap(m_stackHeight - 1, location);
|
appendOrRemoveSwap(m_stackHeight - 1, location);
|
||||||
for (auto arg: arguments)
|
for (auto arg: arguments)
|
||||||
if (canBeRemoved(arg, _c))
|
if (m_classPositions[arg] != c_invalidPosition && canBeRemoved(arg, _c))
|
||||||
m_classPositions[arg] = c_invalidPosition;
|
m_classPositions[arg] = c_invalidPosition;
|
||||||
for (size_t i = 0; i < arguments.size(); ++i)
|
for (size_t i = 0; i < arguments.size(); ++i)
|
||||||
m_stack.erase(m_stackHeight - i);
|
m_stack.erase(m_stackHeight - i);
|
||||||
@ -371,13 +383,22 @@ int CSECodeGenerator::classElementPosition(Id _id) const
|
|||||||
return m_classPositions.at(_id);
|
return m_classPositions.at(_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CSECodeGenerator::canBeRemoved(Id _element, Id _result)
|
bool CSECodeGenerator::canBeRemoved(Id _element, Id _result, int _fromPosition)
|
||||||
{
|
{
|
||||||
// Returns false if _element is finally needed or is needed by a class that has not been
|
// Default for _fromPosition is the canonical position of the element.
|
||||||
// computed yet. Note that m_classPositions also includes classes that were deleted in the meantime.
|
if (_fromPosition == c_invalidPosition)
|
||||||
if (m_finalClasses.count(_element))
|
_fromPosition = classElementPosition(_element);
|
||||||
return false;
|
|
||||||
|
|
||||||
|
bool isCopy = _fromPosition != classElementPosition(_element);
|
||||||
|
if (m_finalClasses.count(_element))
|
||||||
|
// It is part of the target stack. It can be removed if it is a copy that is not in the target position.
|
||||||
|
return isCopy && (!m_targetStack.count(_fromPosition) || m_targetStack[_fromPosition] != _element);
|
||||||
|
else if (isCopy)
|
||||||
|
// It is only a copy, can be removed.
|
||||||
|
return true;
|
||||||
|
|
||||||
|
// Can be removed unless it is needed by a class that has not been computed yet.
|
||||||
|
// Note that m_classPositions also includes classes that were deleted in the meantime.
|
||||||
auto range = m_neededBy.equal_range(_element);
|
auto range = m_neededBy.equal_range(_element);
|
||||||
for (auto it = range.first; it != range.second; ++it)
|
for (auto it = range.first; it != range.second; ++it)
|
||||||
if (it->second != _result && !m_classPositions.count(it->second))
|
if (it->second != _result && !m_classPositions.count(it->second))
|
||||||
@ -391,7 +412,7 @@ bool CSECodeGenerator::removeStackTopIfPossible()
|
|||||||
return false;
|
return false;
|
||||||
assertThrow(m_stack.count(m_stackHeight) > 0, OptimizerException, "");
|
assertThrow(m_stack.count(m_stackHeight) > 0, OptimizerException, "");
|
||||||
Id top = m_stack[m_stackHeight];
|
Id top = m_stack[m_stackHeight];
|
||||||
if (!canBeRemoved(top))
|
if (!canBeRemoved(top, Id(-1), m_stackHeight))
|
||||||
return false;
|
return false;
|
||||||
m_generatedItems.push_back(AssemblyItem(Instruction::POP));
|
m_generatedItems.push_back(AssemblyItem(Instruction::POP));
|
||||||
m_stack.erase(m_stackHeight);
|
m_stack.erase(m_stackHeight);
|
||||||
|
@ -71,13 +71,6 @@ public:
|
|||||||
/// @returns the resulting items after optimization.
|
/// @returns the resulting items after optimization.
|
||||||
AssemblyItems getOptimizedItems();
|
AssemblyItems getOptimizedItems();
|
||||||
|
|
||||||
/// Streams debugging information to @a _out.
|
|
||||||
std::ostream& stream(
|
|
||||||
std::ostream& _out,
|
|
||||||
std::map<int, Id> _initialStack = std::map<int, Id>(),
|
|
||||||
std::map<int, Id> _targetStack = std::map<int, Id>()
|
|
||||||
) const;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/// Feeds the item into the system for analysis.
|
/// Feeds the item into the system for analysis.
|
||||||
void feedItem(AssemblyItem const& _item, bool _copyItem = false);
|
void feedItem(AssemblyItem const& _item, bool _copyItem = false);
|
||||||
@ -134,8 +127,9 @@ private:
|
|||||||
/// @note throws an exception if it is not on the stack.
|
/// @note throws an exception if it is not on the stack.
|
||||||
int classElementPosition(Id _id) const;
|
int classElementPosition(Id _id) const;
|
||||||
|
|
||||||
/// @returns true if @a _element can be removed - in general or, if given, while computing @a _result.
|
/// @returns true if the copy of @a _element can be removed from stack position _fromPosition
|
||||||
bool canBeRemoved(Id _element, Id _result = Id(-1));
|
/// - in general or, if given, while computing @a _result.
|
||||||
|
bool canBeRemoved(Id _element, Id _result = Id(-1), int _fromPosition = c_invalidPosition);
|
||||||
|
|
||||||
/// Appends code to remove the topmost stack element if it can be removed.
|
/// Appends code to remove the topmost stack element if it can be removed.
|
||||||
bool removeStackTopIfPossible();
|
bool removeStackTopIfPossible();
|
||||||
@ -167,6 +161,7 @@ private:
|
|||||||
std::map<std::pair<StoreOperation::Target, Id>, StoreOperations> m_storeOperations;
|
std::map<std::pair<StoreOperation::Target, Id>, StoreOperations> m_storeOperations;
|
||||||
/// The set of equivalence classes that should be present on the stack at the end.
|
/// The set of equivalence classes that should be present on the stack at the end.
|
||||||
std::set<Id> m_finalClasses;
|
std::set<Id> m_finalClasses;
|
||||||
|
std::map<int, Id> m_targetStack;
|
||||||
};
|
};
|
||||||
|
|
||||||
template <class _AssemblyItemIterator>
|
template <class _AssemblyItemIterator>
|
||||||
@ -175,6 +170,7 @@ _AssemblyItemIterator CommonSubexpressionEliminator::feedItems(
|
|||||||
_AssemblyItemIterator _end
|
_AssemblyItemIterator _end
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
|
assertThrow(!m_breakingItem, OptimizerException, "Invalid use of CommonSubexpressionEliminator.");
|
||||||
for (; _iterator != _end && !SemanticInformation::breaksCSEAnalysisBlock(*_iterator); ++_iterator)
|
for (; _iterator != _end && !SemanticInformation::breaksCSEAnalysisBlock(*_iterator); ++_iterator)
|
||||||
feedItem(*_iterator);
|
feedItem(*_iterator);
|
||||||
if (_iterator != _end)
|
if (_iterator != _end)
|
||||||
|
@ -142,7 +142,7 @@ void ControlFlowGraph::removeUnusedBlocks()
|
|||||||
BasicBlock const& block = m_blocks.at(blocksToProcess.back());
|
BasicBlock const& block = m_blocks.at(blocksToProcess.back());
|
||||||
blocksToProcess.pop_back();
|
blocksToProcess.pop_back();
|
||||||
for (BlockId tag: block.pushedTags)
|
for (BlockId tag: block.pushedTags)
|
||||||
if (!neededBlocks.count(tag))
|
if (!neededBlocks.count(tag) && m_blocks.count(tag))
|
||||||
{
|
{
|
||||||
neededBlocks.insert(tag);
|
neededBlocks.insert(tag);
|
||||||
blocksToProcess.push_back(tag);
|
blocksToProcess.push_back(tag);
|
||||||
@ -191,12 +191,12 @@ void ControlFlowGraph::setPrevLinks()
|
|||||||
if (push.type() != PushTag)
|
if (push.type() != PushTag)
|
||||||
continue;
|
continue;
|
||||||
BlockId nextId(push.data());
|
BlockId nextId(push.data());
|
||||||
if (m_blocks.at(nextId).prev)
|
if (m_blocks.count(nextId) && m_blocks.at(nextId).prev)
|
||||||
continue;
|
continue;
|
||||||
bool hasLoop = false;
|
bool hasLoop = false;
|
||||||
for (BlockId id = nextId; id && !hasLoop; id = m_blocks.at(id).next)
|
for (BlockId id = nextId; id && m_blocks.count(id) && !hasLoop; id = m_blocks.at(id).next)
|
||||||
hasLoop = (id == blockId);
|
hasLoop = (id == blockId);
|
||||||
if (hasLoop)
|
if (hasLoop || !m_blocks.count(nextId))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
m_blocks[nextId].prev = blockId;
|
m_blocks[nextId].prev = blockId;
|
||||||
@ -225,6 +225,8 @@ void ControlFlowGraph::gatherKnowledge()
|
|||||||
{
|
{
|
||||||
//@todo we might have to do something like incrementing the sequence number for each JUMPDEST
|
//@todo we might have to do something like incrementing the sequence number for each JUMPDEST
|
||||||
assertThrow(!!workQueue.back().first, OptimizerException, "");
|
assertThrow(!!workQueue.back().first, OptimizerException, "");
|
||||||
|
if (!m_blocks.count(workQueue.back().first))
|
||||||
|
continue; // too bad, we do not know the tag, probably an invalid jump
|
||||||
BasicBlock& block = m_blocks.at(workQueue.back().first);
|
BasicBlock& block = m_blocks.at(workQueue.back().first);
|
||||||
KnownStatePointer state = workQueue.back().second;
|
KnownStatePointer state = workQueue.back().second;
|
||||||
workQueue.pop_back();
|
workQueue.pop_back();
|
||||||
@ -281,6 +283,15 @@ void ControlFlowGraph::gatherKnowledge()
|
|||||||
)
|
)
|
||||||
workQueue.push_back(make_pair(block.next, state->copy()));
|
workQueue.push_back(make_pair(block.next, state->copy()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remove all blocks we never visited here. This might happen because a tag is pushed but
|
||||||
|
// never used for a JUMP.
|
||||||
|
// Note that this invalidates some contents of pushedTags
|
||||||
|
for (auto it = m_blocks.begin(); it != m_blocks.end();)
|
||||||
|
if (!it->second.startState)
|
||||||
|
m_blocks.erase(it++);
|
||||||
|
else
|
||||||
|
it++;
|
||||||
}
|
}
|
||||||
|
|
||||||
BasicBlocks ControlFlowGraph::rebuildCode()
|
BasicBlocks ControlFlowGraph::rebuildCode()
|
||||||
@ -288,6 +299,7 @@ BasicBlocks ControlFlowGraph::rebuildCode()
|
|||||||
map<BlockId, unsigned> pushes;
|
map<BlockId, unsigned> pushes;
|
||||||
for (auto& idAndBlock: m_blocks)
|
for (auto& idAndBlock: m_blocks)
|
||||||
for (BlockId ref: idAndBlock.second.pushedTags)
|
for (BlockId ref: idAndBlock.second.pushedTags)
|
||||||
|
if (m_blocks.count(ref))
|
||||||
pushes[ref]++;
|
pushes[ref]++;
|
||||||
|
|
||||||
set<BlockId> blocksToAdd;
|
set<BlockId> blocksToAdd;
|
||||||
|
Loading…
Reference in New Issue
Block a user