Use hash of candidates for CSE.

This commit is contained in:
chriseth 2022-11-07 16:13:05 +01:00
parent 9db2da0385
commit 7950c2d82c
5 changed files with 139 additions and 46 deletions

View File

@ -20,7 +20,6 @@
*/
#include <libyul/optimiser/BlockHasher.h>
#include <libyul/optimiser/SyntacticalEquality.h>
#include <libyul/AST.h>
#include <libyul/Utilities.h>
@ -195,3 +194,35 @@ void BlockHasher::operator()(Block const& _block)
for (auto& externalReference: subBlockHasher.m_externalReferences)
(*this)(Identifier{{}, externalReference});
}
uint64_t ExpressionHasher::run(Expression const& _e)
{
ExpressionHasher expressionHasher;
expressionHasher.visit(_e);
return expressionHasher.m_hash;
}
void ExpressionHasher::operator()(Literal const& _literal)
{
hash64(compileTimeLiteralHash("Literal"));
if (_literal.kind == LiteralKind::Number)
hash64(std::hash<u256>{}(valueOfNumberLiteral(_literal)));
else
hash64(_literal.value.hash());
hash64(_literal.type.hash());
hash8(static_cast<uint8_t>(_literal.kind));
}
void ExpressionHasher::operator()(Identifier const& _identifier)
{
hash64(compileTimeLiteralHash("Identifier"));
hash64(_identifier.name.hash());
}
void ExpressionHasher::operator()(FunctionCall const& _funCall)
{
hash64(compileTimeLiteralHash("FunctionCall"));
hash64(_funCall.functionName.name.hash());
hash64(_funCall.arguments.size());
ASTWalker::operator()(_funCall);
}

View File

@ -27,6 +27,38 @@
namespace solidity::yul
{
class HasherBase
{
public:
static constexpr uint64_t fnvPrime = 1099511628211u;
static constexpr uint64_t fnvEmptyHash = 14695981039346656037u;
protected:
void hash8(uint8_t _value)
{
m_hash *= fnvPrime;
m_hash ^= _value;
}
void hash16(uint16_t _value)
{
hash8(static_cast<uint8_t>(_value & 0xFF));
hash8(static_cast<uint8_t>(_value >> 8));
}
void hash32(uint32_t _value)
{
hash16(static_cast<uint16_t>(_value & 0xFFFF));
hash16(static_cast<uint16_t>(_value >> 16));
}
void hash64(uint64_t _value)
{
hash32(static_cast<uint32_t>(_value & 0xFFFFFFFF));
hash32(static_cast<uint32_t>(_value >> 32));
}
uint64_t m_hash = fnvEmptyHash;
};
/**
* Optimiser component that calculates hash values for blocks.
* Syntactically equal blocks will have identical hashes and
@ -41,7 +73,7 @@ namespace solidity::yul
*
* Prerequisite: Disambiguator, ForLoopInitRewriter
*/
class BlockHasher: public ASTWalker
class BlockHasher: public ASTWalker, public HasherBase
{
public:
@ -64,36 +96,12 @@ public:
static std::map<Block const*, uint64_t> run(Block const& _block);
static constexpr uint64_t fnvPrime = 1099511628211u;
static constexpr uint64_t fnvEmptyHash = 14695981039346656037u;
private:
BlockHasher(std::map<Block const*, uint64_t>& _blockHashes): m_blockHashes(_blockHashes) {}
void hash8(uint8_t _value)
{
m_hash *= fnvPrime;
m_hash ^= _value;
}
void hash16(uint16_t _value)
{
hash8(static_cast<uint8_t>(_value & 0xFF));
hash8(static_cast<uint8_t>(_value >> 8));
}
void hash32(uint32_t _value)
{
hash16(static_cast<uint16_t>(_value & 0xFFFF));
hash16(static_cast<uint16_t>(_value >> 16));
}
void hash64(uint64_t _value)
{
hash32(static_cast<uint32_t>(_value & 0xFFFFFFFF));
hash32(static_cast<uint32_t>(_value >> 32));
}
std::map<Block const*, uint64_t>& m_blockHashes;
uint64_t m_hash = fnvEmptyHash;
struct VariableReference
{
size_t id = 0;
@ -106,4 +114,24 @@ private:
};
/**
* Computes hashes of expressions that are likely different for syntactically different expressions.
* In contrast to the BlockHasher, hashes of identifiers are likely different if the identifiers
* have a different name and the same if the name matches.
* This means this hasher should only be used on disambiguated sources.
*/
class ExpressionHasher: public ASTWalker, public HasherBase
{
public:
/// Computes a hash of an expression that (in contrast to the behaviour of the class)
/// distinguishes (up to hash collisions) variables with different names.
static uint64_t run(Expression const& _e);
using ASTWalker::operator();
void operator()(Literal const&) override;
void operator()(Identifier const&) override;
void operator()(FunctionCall const& _funCall) override;
};
}

View File

@ -22,6 +22,7 @@
#include <libyul/optimiser/CommonSubexpressionEliminator.h>
#include <libyul/optimiser/SyntacticalEquality.h>
#include <libyul/optimiser/BlockHasher.h>
#include <libyul/optimiser/CallGraphGenerator.h>
#include <libyul/optimiser/Semantics.h>
#include <libyul/SideEffects.h>
@ -55,6 +56,7 @@ CommonSubexpressionEliminator::CommonSubexpressionEliminator(
void CommonSubexpressionEliminator::operator()(FunctionDefinition& _fun)
{
ScopedSaveAndRestore returnVariables(m_returnVariables, {});
ScopedSaveAndRestore replacementCandidates(m_replacementCandidates, {});
for (auto const& v: _fun.returnVariables)
m_returnVariables.insert(v.name);
@ -103,25 +105,44 @@ void CommonSubexpressionEliminator::visit(Expression& _e)
_e = Identifier{debugDataOf(_e), value->name};
}
}
else
{
// TODO this search is rather inefficient.
for (auto const& [variable, value]: allValues())
{
assertThrow(value.value, OptimizerException, "");
// Prevent using the default value of return variables
// instead of literal zeros.
if (
m_returnVariables.count(variable) &&
holds_alternative<Literal>(*value.value) &&
valueOfLiteral(get<Literal>(*value.value)) == 0
)
continue;
if (SyntacticallyEqual{}(_e, *value.value) && inScope(variable))
else if (
auto it = m_replacementCandidates.find(&_e);
it != m_replacementCandidates.end()
)
for (auto const& variable: it->second)
if (AssignedValue const* value = variableValue(variable))
{
_e = Identifier{debugDataOf(_e), variable};
break;
assertThrow(value->value, OptimizerException, "");
// Prevent using the default value of return variables
// instead of literal zeros.
if (
m_returnVariables.count(variable) &&
holds_alternative<Literal>(*value->value) &&
valueOfLiteral(get<Literal>(*value->value)) == 0
)
continue;
// We check for syntactic equality again because the value might have changed.
if (inScope(variable) && SyntacticallyEqual{}(_e, *value->value))
{
_e = Identifier{debugDataOf(_e), variable};
break;
}
}
}
}
}
void CommonSubexpressionEliminator::assignValue(YulString _variable, Expression const* _value)
{
if (_value)
m_replacementCandidates[_value].insert(_variable);
DataFlowAnalyzer::assignValue(_variable, _value);
}
uint64_t CommonSubexpressionEliminator::ExpressionHash::operator()(Expression const* _e) const
{
return ExpressionHasher::run(*_e);
}
bool CommonSubexpressionEliminator::ExpressionEqual::operator()(Expression const* _a, Expression const* _b) const
{
return SyntacticallyEqual{}(*_a, *_b);
}

View File

@ -58,8 +58,21 @@ protected:
using ASTModifier::visit;
void visit(Expression& _e) override;
void assignValue(YulString _variable, Expression const* _value) override;
private:
struct ExpressionHash {
uint64_t operator()(Expression const*) const;
};
struct ExpressionEqual {
bool operator()(Expression const*, Expression const*) const;
};
std::set<YulString> m_returnVariables;
std::unordered_map<
Expression const*,
std::set<YulString>,
ExpressionHash,
ExpressionEqual
> m_replacementCandidates;
};
}

View File

@ -123,7 +123,7 @@ protected:
/// for example at points where control flow is merged.
void clearValues(std::set<YulString> _names);
void assignValue(YulString _variable, Expression const* _value);
virtual void assignValue(YulString _variable, Expression const* _value);
/// Clears knowledge about storage or memory if they may be modified inside the block.
void clearKnowledgeIfInvalidated(Block const& _block);