diff --git a/libyul/optimiser/BlockHasher.cpp b/libyul/optimiser/BlockHasher.cpp index 53a357739..ce07ac9f7 100644 --- a/libyul/optimiser/BlockHasher.cpp +++ b/libyul/optimiser/BlockHasher.cpp @@ -20,7 +20,6 @@ */ #include -#include #include #include @@ -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{}(valueOfNumberLiteral(_literal))); + else + hash64(_literal.value.hash()); + hash64(_literal.type.hash()); + hash8(static_cast(_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); +} diff --git a/libyul/optimiser/BlockHasher.h b/libyul/optimiser/BlockHasher.h index a831efa23..6dc55371c 100644 --- a/libyul/optimiser/BlockHasher.h +++ b/libyul/optimiser/BlockHasher.h @@ -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(_value & 0xFF)); + hash8(static_cast(_value >> 8)); + } + void hash32(uint32_t _value) + { + hash16(static_cast(_value & 0xFFFF)); + hash16(static_cast(_value >> 16)); + } + void hash64(uint64_t _value) + { + hash32(static_cast(_value & 0xFFFFFFFF)); + hash32(static_cast(_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 run(Block const& _block); - static constexpr uint64_t fnvPrime = 1099511628211u; - static constexpr uint64_t fnvEmptyHash = 14695981039346656037u; private: BlockHasher(std::map& _blockHashes): m_blockHashes(_blockHashes) {} - void hash8(uint8_t _value) - { - m_hash *= fnvPrime; - m_hash ^= _value; - } - void hash16(uint16_t _value) - { - hash8(static_cast(_value & 0xFF)); - hash8(static_cast(_value >> 8)); - } - void hash32(uint32_t _value) - { - hash16(static_cast(_value & 0xFFFF)); - hash16(static_cast(_value >> 16)); - } - void hash64(uint64_t _value) - { - hash32(static_cast(_value & 0xFFFFFFFF)); - hash32(static_cast(_value >> 32)); - } - std::map& 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); + } +}; + } diff --git a/libyul/optimiser/CommonSubexpressionEliminator.cpp b/libyul/optimiser/CommonSubexpressionEliminator.cpp index d1d482877..5d085cdd4 100644 --- a/libyul/optimiser/CommonSubexpressionEliminator.cpp +++ b/libyul/optimiser/CommonSubexpressionEliminator.cpp @@ -22,6 +22,7 @@ #include #include +#include #include #include #include @@ -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(*value.value) && - valueOfLiteral(get(*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(*value->value) && + valueOfLiteral(get(*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); } diff --git a/libyul/optimiser/CommonSubexpressionEliminator.h b/libyul/optimiser/CommonSubexpressionEliminator.h index 7099ad181..163c9df99 100644 --- a/libyul/optimiser/CommonSubexpressionEliminator.h +++ b/libyul/optimiser/CommonSubexpressionEliminator.h @@ -24,6 +24,8 @@ #include #include +#include +#include #include @@ -58,8 +60,16 @@ protected: using ASTModifier::visit; void visit(Expression& _e) override; + void assignValue(YulString _variable, Expression const* _value) override; private: std::set m_returnVariables; + std::unordered_map< + std::reference_wrapper, + std::set, + ExpressionHash, + SyntacticallyEqualExpression + > m_replacementCandidates; }; + } diff --git a/libyul/optimiser/DataFlowAnalyzer.h b/libyul/optimiser/DataFlowAnalyzer.h index b82f43753..fe5a75928 100644 --- a/libyul/optimiser/DataFlowAnalyzer.h +++ b/libyul/optimiser/DataFlowAnalyzer.h @@ -123,7 +123,7 @@ protected: /// for example at points where control flow is merged. void clearValues(std::set _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); diff --git a/libyul/optimiser/SyntacticalEquality.cpp b/libyul/optimiser/SyntacticalEquality.cpp index c7aed1937..dccdf62a5 100644 --- a/libyul/optimiser/SyntacticalEquality.cpp +++ b/libyul/optimiser/SyntacticalEquality.cpp @@ -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); +} + diff --git a/libyul/optimiser/SyntacticalEquality.h b/libyul/optimiser/SyntacticalEquality.h index 02a6d4b39..319da763e 100644 --- a/libyul/optimiser/SyntacticalEquality.h +++ b/libyul/optimiser/SyntacticalEquality.h @@ -85,4 +85,13 @@ private: std::map 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; +}; + + }