mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Compatibility with StackCompressor and StackLimitEvader.
This commit is contained in:
parent
b2c9b69de2
commit
4229369180
@ -1069,9 +1069,6 @@ string IRGenerator::dispatchRoutine(ContractDefinition const& _contract)
|
|||||||
|
|
||||||
string IRGenerator::memoryInit(bool _useMemoryGuard)
|
string IRGenerator::memoryInit(bool _useMemoryGuard)
|
||||||
{
|
{
|
||||||
// TODO: Remove once we have made sure it is safe, i.e. after "Yul memory objects lite".
|
|
||||||
// Also restore the tests removed in the commit that adds this comment.
|
|
||||||
_useMemoryGuard = false;
|
|
||||||
// This function should be called at the beginning of the EVM call frame
|
// This function should be called at the beginning of the EVM call frame
|
||||||
// and thus can assume all memory to be zero, including the contents of
|
// and thus can assume all memory to be zero, including the contents of
|
||||||
// the "zero memory area" (the position CompilerUtils::zeroPointer points to).
|
// the "zero memory area" (the position CompilerUtils::zeroPointer points to).
|
||||||
|
@ -27,6 +27,13 @@
|
|||||||
#include <libyul/optimiser/Metrics.h>
|
#include <libyul/optimiser/Metrics.h>
|
||||||
#include <libyul/optimiser/Semantics.h>
|
#include <libyul/optimiser/Semantics.h>
|
||||||
|
|
||||||
|
#include <libyul/backends/evm/ControlFlowGraphBuilder.h>
|
||||||
|
#include <libyul/backends/evm/StackHelpers.h>
|
||||||
|
#include <libyul/backends/evm/StackLayoutGenerator.h>
|
||||||
|
|
||||||
|
#include <libyul/AsmAnalysis.h>
|
||||||
|
#include <libyul/AsmAnalysisInfo.h>
|
||||||
|
|
||||||
#include <libyul/CompilabilityChecker.h>
|
#include <libyul/CompilabilityChecker.h>
|
||||||
|
|
||||||
#include <libyul/AST.h>
|
#include <libyul/AST.h>
|
||||||
@ -162,6 +169,50 @@ void eliminateVariables(
|
|||||||
UnusedPruner::runUntilStabilised(_dialect, _node, _allowMSizeOptimization);
|
UnusedPruner::runUntilStabilised(_dialect, _node, _allowMSizeOptimization);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void eliminateVariables(
|
||||||
|
Dialect const& _dialect,
|
||||||
|
Block& _block,
|
||||||
|
vector<StackLayoutGenerator::StackTooDeep> const& _unreachables,
|
||||||
|
bool _allowMSizeOptimization
|
||||||
|
)
|
||||||
|
{
|
||||||
|
RematCandidateSelector selector{_dialect};
|
||||||
|
selector(_block);
|
||||||
|
std::map<YulString, size_t> candidates;
|
||||||
|
for (auto [cost, candidatesWithCost]: selector.candidates())
|
||||||
|
for (auto candidate: candidatesWithCost)
|
||||||
|
candidates[get<0>(candidate)] = cost;
|
||||||
|
|
||||||
|
set<YulString> varsToEliminate;
|
||||||
|
|
||||||
|
// TODO: this currently ignores the fact that variables may reference other variables we want to eliminate.
|
||||||
|
for (auto const& unreachable: _unreachables)
|
||||||
|
{
|
||||||
|
map<size_t, vector<YulString>> suitableCandidates;
|
||||||
|
size_t neededSlots = unreachable.deficit;
|
||||||
|
for (auto varName: unreachable.variableChoices)
|
||||||
|
{
|
||||||
|
if (varsToEliminate.count(varName))
|
||||||
|
--neededSlots;
|
||||||
|
else if (size_t* cost = util::valueOrNullptr(candidates, varName))
|
||||||
|
if (!util::contains(suitableCandidates[*cost], varName))
|
||||||
|
suitableCandidates[*cost].emplace_back(varName);
|
||||||
|
}
|
||||||
|
for (auto candidatesByCost: suitableCandidates)
|
||||||
|
{
|
||||||
|
for (auto candidate: candidatesByCost.second)
|
||||||
|
if (neededSlots--)
|
||||||
|
varsToEliminate.emplace(candidate);
|
||||||
|
else
|
||||||
|
break;
|
||||||
|
if (!neededSlots)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Rematerialiser::run(_dialect, _block, std::move(varsToEliminate), true);
|
||||||
|
UnusedPruner::runUntilStabilised(_dialect, _block, _allowMSizeOptimization);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool StackCompressor::run(
|
bool StackCompressor::run(
|
||||||
@ -176,39 +227,66 @@ bool StackCompressor::run(
|
|||||||
_object.code->statements.size() > 0 && holds_alternative<Block>(_object.code->statements.at(0)),
|
_object.code->statements.size() > 0 && holds_alternative<Block>(_object.code->statements.at(0)),
|
||||||
"Need to run the function grouper before the stack compressor."
|
"Need to run the function grouper before the stack compressor."
|
||||||
);
|
);
|
||||||
|
bool usesOptimizedCodeGenerator = false;
|
||||||
|
if (auto evmDialect = dynamic_cast<EVMDialect const*>(&_dialect))
|
||||||
|
usesOptimizedCodeGenerator =
|
||||||
|
_optimizeStackAllocation &&
|
||||||
|
evmDialect->evmVersion().canOverchargeGasForCall() &&
|
||||||
|
evmDialect->providesObjectAccess();
|
||||||
bool allowMSizeOptimzation = !MSizeFinder::containsMSize(_dialect, *_object.code);
|
bool allowMSizeOptimzation = !MSizeFinder::containsMSize(_dialect, *_object.code);
|
||||||
for (size_t iterations = 0; iterations < _maxIterations; iterations++)
|
if (usesOptimizedCodeGenerator)
|
||||||
{
|
{
|
||||||
map<YulString, int> stackSurplus = CompilabilityChecker(_dialect, _object, _optimizeStackAllocation).stackDeficit;
|
yul::AsmAnalysisInfo analysisInfo = yul::AsmAnalyzer::analyzeStrictAssertCorrect(_dialect, _object);
|
||||||
if (stackSurplus.empty())
|
unique_ptr<CFG> cfg = ControlFlowGraphBuilder::build(analysisInfo, _dialect, *_object.code);
|
||||||
return true;
|
Block& mainBlock = std::get<Block>(_object.code->statements.at(0));
|
||||||
|
if (
|
||||||
if (stackSurplus.count(YulString{}))
|
auto stackTooDeepErrors = StackLayoutGenerator::reportStackTooDeep(*cfg, YulString{});
|
||||||
{
|
!stackTooDeepErrors.empty()
|
||||||
yulAssert(stackSurplus.at({}) > 0, "Invalid surplus value.");
|
)
|
||||||
eliminateVariables(
|
eliminateVariables(_dialect, mainBlock, stackTooDeepErrors, allowMSizeOptimzation);
|
||||||
_dialect,
|
|
||||||
std::get<Block>(_object.code->statements.at(0)),
|
|
||||||
static_cast<size_t>(stackSurplus.at({})),
|
|
||||||
allowMSizeOptimzation
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (size_t i = 1; i < _object.code->statements.size(); ++i)
|
for (size_t i = 1; i < _object.code->statements.size(); ++i)
|
||||||
{
|
{
|
||||||
auto& fun = std::get<FunctionDefinition>(_object.code->statements[i]);
|
auto& fun = std::get<FunctionDefinition>(_object.code->statements[i]);
|
||||||
if (!stackSurplus.count(fun.name))
|
if (
|
||||||
continue;
|
auto stackTooDeepErrors = StackLayoutGenerator::reportStackTooDeep(*cfg, fun.name);
|
||||||
|
!stackTooDeepErrors.empty()
|
||||||
yulAssert(stackSurplus.at(fun.name) > 0, "Invalid surplus value.");
|
)
|
||||||
eliminateVariables(
|
eliminateVariables(_dialect, fun.body, stackTooDeepErrors, allowMSizeOptimzation);
|
||||||
_dialect,
|
|
||||||
fun,
|
|
||||||
static_cast<size_t>(stackSurplus.at(fun.name)),
|
|
||||||
allowMSizeOptimzation
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
for (size_t iterations = 0; iterations < _maxIterations; iterations++)
|
||||||
|
{
|
||||||
|
map<YulString, int> stackSurplus = CompilabilityChecker(_dialect, _object, _optimizeStackAllocation).stackDeficit;
|
||||||
|
if (stackSurplus.empty())
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (stackSurplus.count(YulString{}))
|
||||||
|
{
|
||||||
|
yulAssert(stackSurplus.at({}) > 0, "Invalid surplus value.");
|
||||||
|
eliminateVariables(
|
||||||
|
_dialect,
|
||||||
|
std::get<Block>(_object.code->statements.at(0)),
|
||||||
|
static_cast<size_t>(stackSurplus.at({})),
|
||||||
|
allowMSizeOptimzation
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t i = 1; i < _object.code->statements.size(); ++i)
|
||||||
|
{
|
||||||
|
auto& fun = std::get<FunctionDefinition>(_object.code->statements[i]);
|
||||||
|
if (!stackSurplus.count(fun.name))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
yulAssert(stackSurplus.at(fun.name) > 0, "Invalid surplus value.");
|
||||||
|
eliminateVariables(
|
||||||
|
_dialect,
|
||||||
|
fun,
|
||||||
|
static_cast<size_t>(stackSurplus.at(fun.name)),
|
||||||
|
allowMSizeOptimzation
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,14 +21,18 @@
|
|||||||
#include <libyul/optimiser/FunctionDefinitionCollector.h>
|
#include <libyul/optimiser/FunctionDefinitionCollector.h>
|
||||||
#include <libyul/optimiser/NameDispenser.h>
|
#include <libyul/optimiser/NameDispenser.h>
|
||||||
#include <libyul/optimiser/StackToMemoryMover.h>
|
#include <libyul/optimiser/StackToMemoryMover.h>
|
||||||
|
#include <libyul/backends/evm/ControlFlowGraphBuilder.h>
|
||||||
#include <libyul/backends/evm/EVMDialect.h>
|
#include <libyul/backends/evm/EVMDialect.h>
|
||||||
|
#include <libyul/AsmAnalysis.h>
|
||||||
#include <libyul/AST.h>
|
#include <libyul/AST.h>
|
||||||
|
#include <libyul/CompilabilityChecker.h>
|
||||||
#include <libyul/Exceptions.h>
|
#include <libyul/Exceptions.h>
|
||||||
#include <libyul/Object.h>
|
#include <libyul/Object.h>
|
||||||
#include <libyul/Utilities.h>
|
#include <libyul/Utilities.h>
|
||||||
#include <libsolutil/Algorithms.h>
|
#include <libsolutil/Algorithms.h>
|
||||||
#include <libsolutil/CommonData.h>
|
#include <libsolutil/CommonData.h>
|
||||||
|
|
||||||
|
#include <range/v3/range/conversion.hpp>
|
||||||
#include <range/v3/view/concat.hpp>
|
#include <range/v3/view/concat.hpp>
|
||||||
#include <range/v3/view/take.hpp>
|
#include <range/v3/view/take.hpp>
|
||||||
|
|
||||||
@ -114,6 +118,45 @@ u256 literalArgumentValue(FunctionCall const& _call)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void StackLimitEvader::run(
|
||||||
|
OptimiserStepContext& _context,
|
||||||
|
Object& _object
|
||||||
|
)
|
||||||
|
{
|
||||||
|
auto const* evmDialect = dynamic_cast<EVMDialect const*>(&_context.dialect);
|
||||||
|
yulAssert(
|
||||||
|
evmDialect && evmDialect->providesObjectAccess(),
|
||||||
|
"StackLimitEvader can only be run on objects using the EVMDialect with object access."
|
||||||
|
);
|
||||||
|
if (evmDialect && evmDialect->evmVersion().canOverchargeGasForCall())
|
||||||
|
{
|
||||||
|
yul::AsmAnalysisInfo analysisInfo = yul::AsmAnalyzer::analyzeStrictAssertCorrect(*evmDialect, _object);
|
||||||
|
unique_ptr<CFG> cfg = ControlFlowGraphBuilder::build(analysisInfo, *evmDialect, *_object.code);
|
||||||
|
run(_context, _object, StackLayoutGenerator::reportStackTooDeep(*cfg));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
run(_context, _object, CompilabilityChecker{
|
||||||
|
_context.dialect,
|
||||||
|
_object,
|
||||||
|
true
|
||||||
|
}.unreachableVariables);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void StackLimitEvader::run(
|
||||||
|
OptimiserStepContext& _context,
|
||||||
|
Object& _object,
|
||||||
|
map<YulString, vector<StackLayoutGenerator::StackTooDeep>> const& _stackTooDeepErrors
|
||||||
|
)
|
||||||
|
{
|
||||||
|
map<YulString, set<YulString>> unreachableVariables;
|
||||||
|
for (auto&& [function, stackTooDeepErrors]: _stackTooDeepErrors)
|
||||||
|
// TODO: choose wisely.
|
||||||
|
for (auto const& stackTooDeepError: stackTooDeepErrors)
|
||||||
|
unreachableVariables[function] += stackTooDeepError.variableChoices | ranges::views::take(stackTooDeepError.deficit) | ranges::to<set<YulString>>;
|
||||||
|
run(_context, _object, unreachableVariables);
|
||||||
|
}
|
||||||
|
|
||||||
void StackLimitEvader::run(
|
void StackLimitEvader::run(
|
||||||
OptimiserStepContext& _context,
|
OptimiserStepContext& _context,
|
||||||
Object& _object,
|
Object& _object,
|
||||||
|
@ -22,6 +22,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <libyul/optimiser/OptimiserStep.h>
|
#include <libyul/optimiser/OptimiserStep.h>
|
||||||
|
#include <libyul/backends/evm/StackLayoutGenerator.h>
|
||||||
|
|
||||||
namespace solidity::yul
|
namespace solidity::yul
|
||||||
{
|
{
|
||||||
@ -61,6 +62,25 @@ public:
|
|||||||
Object& _object,
|
Object& _object,
|
||||||
std::map<YulString, std::set<YulString>> const& _unreachableVariables
|
std::map<YulString, std::set<YulString>> const& _unreachableVariables
|
||||||
);
|
);
|
||||||
|
/// @a _stackTooDeepErrors can be determined by the StackLayoutGenerator.
|
||||||
|
/// Can only be run on the EVM dialect with objects.
|
||||||
|
/// Abort and do nothing, if no ``memoryguard`` call or several ``memoryguard`` calls
|
||||||
|
/// with non-matching arguments are found, or if any of the @a _stackTooDeepErrors
|
||||||
|
/// are contained in a recursive function.
|
||||||
|
static void run(
|
||||||
|
OptimiserStepContext& _context,
|
||||||
|
Object& _object,
|
||||||
|
std::map<YulString, std::vector<StackLayoutGenerator::StackTooDeep>> const& _stackTooDeepErrors
|
||||||
|
);
|
||||||
|
/// Determines stack too deep errors using the appropriate code generation backend.
|
||||||
|
/// Can only be run on the EVM dialect with objects.
|
||||||
|
/// Abort and do nothing, if no ``memoryguard`` call or several ``memoryguard`` calls
|
||||||
|
/// with non-matching arguments are found, or if any of the unreachable variables
|
||||||
|
/// are contained in a recursive function.
|
||||||
|
static void run(
|
||||||
|
OptimiserStepContext& _context,
|
||||||
|
Object& _object
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -95,6 +95,12 @@ void OptimiserSuite::run(
|
|||||||
set<YulString> const& _externallyUsedIdentifiers
|
set<YulString> const& _externallyUsedIdentifiers
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
|
EVMDialect const* evmDialect = dynamic_cast<EVMDialect const*>(&_dialect);
|
||||||
|
bool usesOptimizedCodeGenerator =
|
||||||
|
_optimizeStackAllocation &&
|
||||||
|
evmDialect &&
|
||||||
|
evmDialect->evmVersion().canOverchargeGasForCall() &&
|
||||||
|
evmDialect->providesObjectAccess();
|
||||||
set<YulString> reservedIdentifiers = _externallyUsedIdentifiers;
|
set<YulString> reservedIdentifiers = _externallyUsedIdentifiers;
|
||||||
reservedIdentifiers += _dialect.fixedFunctionNames();
|
reservedIdentifiers += _dialect.fixedFunctionNames();
|
||||||
|
|
||||||
@ -121,24 +127,32 @@ void OptimiserSuite::run(
|
|||||||
|
|
||||||
// We ignore the return value because we will get a much better error
|
// We ignore the return value because we will get a much better error
|
||||||
// message once we perform code generation.
|
// message once we perform code generation.
|
||||||
StackCompressor::run(
|
if (!usesOptimizedCodeGenerator)
|
||||||
_dialect,
|
StackCompressor::run(
|
||||||
_object,
|
_dialect,
|
||||||
_optimizeStackAllocation,
|
_object,
|
||||||
stackCompressorMaxIterations
|
_optimizeStackAllocation,
|
||||||
);
|
stackCompressorMaxIterations
|
||||||
|
);
|
||||||
suite.runSequence("fDnTOc g", ast);
|
suite.runSequence("fDnTOc g", ast);
|
||||||
|
|
||||||
if (EVMDialect const* dialect = dynamic_cast<EVMDialect const*>(&_dialect))
|
if (evmDialect)
|
||||||
{
|
{
|
||||||
yulAssert(_meter, "");
|
yulAssert(_meter, "");
|
||||||
ConstantOptimiser{*dialect, *_meter}(ast);
|
ConstantOptimiser{*evmDialect, *_meter}(ast);
|
||||||
if (dialect->providesObjectAccess() && _optimizeStackAllocation)
|
if (usesOptimizedCodeGenerator)
|
||||||
StackLimitEvader::run(suite.m_context, _object, CompilabilityChecker{
|
{
|
||||||
|
StackCompressor::run(
|
||||||
_dialect,
|
_dialect,
|
||||||
_object,
|
_object,
|
||||||
_optimizeStackAllocation
|
_optimizeStackAllocation,
|
||||||
}.unreachableVariables);
|
stackCompressorMaxIterations
|
||||||
|
);
|
||||||
|
if (evmDialect->providesObjectAccess())
|
||||||
|
StackLimitEvader::run(suite.m_context, _object);
|
||||||
|
}
|
||||||
|
else if (evmDialect->providesObjectAccess() && _optimizeStackAllocation)
|
||||||
|
StackLimitEvader::run(suite.m_context, _object);
|
||||||
}
|
}
|
||||||
else if (dynamic_cast<WasmDialect const*>(&_dialect))
|
else if (dynamic_cast<WasmDialect const*>(&_dialect))
|
||||||
{
|
{
|
||||||
|
Loading…
Reference in New Issue
Block a user