Evaluate keccak(a, const) if value at memory location a is known

Here the value of constant can be at most 32.
This commit is contained in:
hrkrshnn 2021-03-01 19:05:59 +01:00
parent 011f8d3ff7
commit 3bc4f5708a
4 changed files with 103 additions and 4 deletions

View File

@ -27,6 +27,7 @@
#include <libyul/AST.h> #include <libyul/AST.h>
#include <libyul/Dialect.h> #include <libyul/Dialect.h>
#include <libyul/Exceptions.h> #include <libyul/Exceptions.h>
#include <libyul/Utilities.h>
#include <libsolutil/CommonData.h> #include <libsolutil/CommonData.h>
#include <libsolutil/cxx20.h> #include <libsolutil/cxx20.h>
@ -388,6 +389,14 @@ bool DataFlowAnalyzer::inScope(YulString _variableName) const
return false; return false;
} }
optional<u256> DataFlowAnalyzer::valueOfIdentifier(YulString const& _name)
{
if (m_value.count(_name))
if (Literal const* literal = get_if<Literal>(m_value.at(_name).value))
return valueOfLiteral(*literal);
return nullopt;
}
std::optional<pair<YulString, YulString>> DataFlowAnalyzer::isSimpleStore( std::optional<pair<YulString, YulString>> DataFlowAnalyzer::isSimpleStore(
StoreLoadLocation _location, StoreLoadLocation _location,
ExpressionStatement const& _statement ExpressionStatement const& _statement
@ -412,4 +421,3 @@ std::optional<YulString> DataFlowAnalyzer::isSimpleLoad(
return key->name; return key->name;
return {}; return {};
} }

View File

@ -29,6 +29,8 @@
#include <libyul/AST.h> // Needed for m_zero below. #include <libyul/AST.h> // Needed for m_zero below.
#include <libyul/SideEffects.h> #include <libyul/SideEffects.h>
#include <libsolutil/Common.h>
#include <map> #include <map>
#include <set> #include <set>
@ -134,6 +136,9 @@ protected:
/// Returns true iff the variable is in scope. /// Returns true iff the variable is in scope.
bool inScope(YulString _variableName) const; bool inScope(YulString _variableName) const;
/// Returns the literal value of the identifier, if it exists.
std::optional<u256> valueOfIdentifier(YulString const& _name);
enum class StoreLoadLocation { enum class StoreLoadLocation {
Memory = 0, Memory = 0,
Storage = 1, Storage = 1,

View File

@ -23,13 +23,23 @@
#include <libyul/optimiser/LoadResolver.h> #include <libyul/optimiser/LoadResolver.h>
#include <libyul/backends/evm/EVMDialect.h> #include <libyul/backends/evm/EVMDialect.h>
#include <libyul/backends/evm/EVMMetrics.h>
#include <libyul/optimiser/Semantics.h> #include <libyul/optimiser/Semantics.h>
#include <libyul/optimiser/CallGraphGenerator.h> #include <libyul/optimiser/CallGraphGenerator.h>
#include <libyul/SideEffects.h> #include <libyul/SideEffects.h>
#include <libyul/AST.h> #include <libyul/AST.h>
#include <libyul/Utilities.h>
#include <libevmasm/Instruction.h>
#include <libevmasm/GasMeter.h>
#include <libsolutil/Keccak256.h>
#include <limits>
using namespace std; using namespace std;
using namespace solidity; using namespace solidity;
using namespace solidity::util;
using namespace solidity::evmasm;
using namespace solidity::yul; using namespace solidity::yul;
void LoadResolver::run(OptimiserStepContext& _context, Block& _ast) void LoadResolver::run(OptimiserStepContext& _context, Block& _ast)
@ -38,7 +48,8 @@ void LoadResolver::run(OptimiserStepContext& _context, Block& _ast)
LoadResolver{ LoadResolver{
_context.dialect, _context.dialect,
SideEffectsPropagator::sideEffects(_context.dialect, CallGraphGenerator::callGraph(_ast)), SideEffectsPropagator::sideEffects(_context.dialect, CallGraphGenerator::callGraph(_ast)),
!containsMSize !containsMSize,
_context.expectedExecutionsPerDeployment
}(_ast); }(_ast);
} }
@ -47,12 +58,17 @@ void LoadResolver::visit(Expression& _e)
DataFlowAnalyzer::visit(_e); DataFlowAnalyzer::visit(_e);
if (FunctionCall const* funCall = std::get_if<FunctionCall>(&_e)) if (FunctionCall const* funCall = std::get_if<FunctionCall>(&_e))
{
for (auto location: { StoreLoadLocation::Memory, StoreLoadLocation::Storage }) for (auto location: { StoreLoadLocation::Memory, StoreLoadLocation::Storage })
if (funCall->functionName.name == m_loadFunctionName[static_cast<unsigned>(location)]) if (funCall->functionName.name == m_loadFunctionName[static_cast<unsigned>(location)])
{ {
tryResolve(_e, location, funCall->arguments); tryResolve(_e, location, funCall->arguments);
break; break;
} }
if (funCall->functionName.name == m_dialect.hashFunction({}))
tryEvaluateKeccak(_e, funCall->arguments);
}
} }
void LoadResolver::tryResolve( void LoadResolver::tryResolve(
@ -76,3 +92,59 @@ void LoadResolver::tryResolve(
if (inScope(*value)) if (inScope(*value))
_e = Identifier{locationOf(_e), *value}; _e = Identifier{locationOf(_e), *value};
} }
void LoadResolver::tryEvaluateKeccak(
Expression& _e,
std::vector<Expression> const& _arguments
)
{
// The costs are only correct for hashes of 32 bytes or 1 word (when rounded up).
GasMeter gasMeter{
dynamic_cast<EVMDialect const&>(m_dialect),
!m_expectedExecutionsPerDeployment,
m_expectedExecutionsPerDeployment ? *m_expectedExecutionsPerDeployment : 1
};
bigint costOfKeccak = gasMeter.costs(_e);
bigint costOfLiteral = gasMeter.costs(
Literal{
{},
LiteralKind::Number,
// a dummy 256-bit number to represent the Keccak256 hash.
YulString{numeric_limits<u256>::max().str()},
{}
}
);
// We skip if there are no net gas savings.
// Note that for default `m_runs = 200`, the values are
// `costOfLiteral = 7200` and `costOfKeccak = 9000` for runtime context.
// For creation context: `costOfLiteral = 531` and `costOfKeccak = 90`.
if (costOfLiteral > costOfKeccak)
return;
yulAssert(_arguments.size() == 2, "");
Identifier const* memoryKey = std::get_if<Identifier>(&_arguments.at(0));
Identifier const* length = std::get_if<Identifier>(&_arguments.at(1));
if (!memoryKey || !length)
return;
auto memoryValue = util::valueOrNullptr(m_memory, memoryKey->name);
if (memoryValue && inScope(*memoryValue))
{
optional<u256> memoryContent = valueOfIdentifier(*memoryValue);
optional<u256> byteLength = valueOfIdentifier(length->name);
if (memoryContent && byteLength && *byteLength <= 32)
{
bytes contentAsBytes = toBigEndian(*memoryContent);
contentAsBytes.resize(static_cast<size_t>(*byteLength));
_e = Literal{
locationOf(_e),
LiteralKind::Number,
YulString{u256(keccak256(contentAsBytes)).str()},
m_dialect.defaultType
};
}
}
}

View File

@ -32,6 +32,9 @@ namespace solidity::yul
* Optimisation stage that replaces expressions of type ``sload(x)`` and ``mload(x)`` by the value * Optimisation stage that replaces expressions of type ``sload(x)`` and ``mload(x)`` by the value
* currently stored in storage resp. memory, if known. * currently stored in storage resp. memory, if known.
* *
* Also evaluates simple ``keccak256(a, c)`` when the value at memory location `a` is known and `c`
* is a constant `<= 32`.
*
* Works best if the code is in SSA form. * Works best if the code is in SSA form.
* *
* Prerequisite: Disambiguator, ForLoopInitRewriter. * Prerequisite: Disambiguator, ForLoopInitRewriter.
@ -47,10 +50,12 @@ private:
LoadResolver( LoadResolver(
Dialect const& _dialect, Dialect const& _dialect,
std::map<YulString, SideEffects> _functionSideEffects, std::map<YulString, SideEffects> _functionSideEffects,
bool _optimizeMLoad bool _optimizeMLoad,
std::optional<size_t> _expectedExecutionsPerDeployment
): ):
DataFlowAnalyzer(_dialect, std::move(_functionSideEffects)), DataFlowAnalyzer(_dialect, std::move(_functionSideEffects)),
m_optimizeMLoad(_optimizeMLoad) m_optimizeMLoad(_optimizeMLoad),
m_expectedExecutionsPerDeployment(std::move(_expectedExecutionsPerDeployment))
{} {}
protected: protected:
@ -63,7 +68,16 @@ protected:
std::vector<Expression> const& _arguments std::vector<Expression> const& _arguments
); );
/// Evaluates simple ``keccak256(a, c)`` when the value at memory location ``a`` is known and
/// `c` is a constant `<= 32`.
void tryEvaluateKeccak(
Expression& _e,
std::vector<Expression> const& _arguments
);
bool m_optimizeMLoad = false; bool m_optimizeMLoad = false;
/// The --optimize-runs parameter. Value `nullopt` represents creation code.
std::optional<size_t> m_expectedExecutionsPerDeployment;
}; };
} }