Merge pull request #13682 from ethereum/improve_cse

Use hash of candidates for CSE.
This commit is contained in:
chriseth 2022-11-10 16:48:57 +01:00 committed by GitHub
commit 1bd35b1be1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 146 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,32 @@ 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;
};
struct ExpressionHash
{
uint64_t operator()(Expression const& _expression) const
{
return ExpressionHasher::run(_expression);
}
};
}

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,31 @@ 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 const* candidates = util::valueOrNullptr(m_replacementCandidates, _e))
for (auto const& variable: *candidates)
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);
}

View File

@ -24,6 +24,8 @@
#include <libyul/optimiser/DataFlowAnalyzer.h>
#include <libyul/optimiser/OptimiserStep.h>
#include <libyul/optimiser/SyntacticalEquality.h>
#include <libyul/optimiser/BlockHasher.h>
#include <set>
@ -58,8 +60,16 @@ protected:
using ASTModifier::visit;
void visit(Expression& _e) override;
void assignValue(YulString _variable, Expression const* _value) override;
private:
std::set<YulString> m_returnVariables;
std::unordered_map<
std::reference_wrapper<Expression const>,
std::set<YulString>,
ExpressionHash,
SyntacticallyEqualExpression
> 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);

View File

@ -165,3 +165,9 @@ bool SyntacticallyEqual::visitDeclaration(TypedName const& _lhs, TypedName const
m_identifiersRHS[_rhs.name] = id;
return true;
}
bool SyntacticallyEqualExpression::operator()(Expression const& _lhs, Expression const& _rhs) const
{
return SyntacticallyEqual{}(_lhs, _rhs);
}

View File

@ -85,4 +85,13 @@ private:
std::map<YulString, std::size_t> m_identifiersRHS;
};
/**
* Does the same as SyntacticallyEqual just that the operator() function is const.
*/
struct SyntacticallyEqualExpression
{
bool operator()(Expression const& _lhs, Expression const& _rhs) const;
};
}