Compatibility with StackCompressor and StackLimitEvader.

This commit is contained in:
Daniel Kirchner 2021-08-12 17:17:21 +02:00
parent b2c9b69de2
commit 4229369180
5 changed files with 193 additions and 41 deletions

View File

@ -1069,9 +1069,6 @@ string IRGenerator::dispatchRoutine(ContractDefinition const& _contract)
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
// and thus can assume all memory to be zero, including the contents of
// the "zero memory area" (the position CompilerUtils::zeroPointer points to).

View File

@ -27,6 +27,13 @@
#include <libyul/optimiser/Metrics.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/AST.h>
@ -162,6 +169,50 @@ void eliminateVariables(
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(
@ -176,39 +227,66 @@ bool StackCompressor::run(
_object.code->statements.size() > 0 && holds_alternative<Block>(_object.code->statements.at(0)),
"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);
for (size_t iterations = 0; iterations < _maxIterations; iterations++)
if (usesOptimizedCodeGenerator)
{
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
);
}
yul::AsmAnalysisInfo analysisInfo = yul::AsmAnalyzer::analyzeStrictAssertCorrect(_dialect, _object);
unique_ptr<CFG> cfg = ControlFlowGraphBuilder::build(analysisInfo, _dialect, *_object.code);
Block& mainBlock = std::get<Block>(_object.code->statements.at(0));
if (
auto stackTooDeepErrors = StackLayoutGenerator::reportStackTooDeep(*cfg, YulString{});
!stackTooDeepErrors.empty()
)
eliminateVariables(_dialect, mainBlock, stackTooDeepErrors, 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
);
if (
auto stackTooDeepErrors = StackLayoutGenerator::reportStackTooDeep(*cfg, fun.name);
!stackTooDeepErrors.empty()
)
eliminateVariables(_dialect, fun.body, stackTooDeepErrors, 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;
}

View File

@ -21,14 +21,18 @@
#include <libyul/optimiser/FunctionDefinitionCollector.h>
#include <libyul/optimiser/NameDispenser.h>
#include <libyul/optimiser/StackToMemoryMover.h>
#include <libyul/backends/evm/ControlFlowGraphBuilder.h>
#include <libyul/backends/evm/EVMDialect.h>
#include <libyul/AsmAnalysis.h>
#include <libyul/AST.h>
#include <libyul/CompilabilityChecker.h>
#include <libyul/Exceptions.h>
#include <libyul/Object.h>
#include <libyul/Utilities.h>
#include <libsolutil/Algorithms.h>
#include <libsolutil/CommonData.h>
#include <range/v3/range/conversion.hpp>
#include <range/v3/view/concat.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(
OptimiserStepContext& _context,
Object& _object,

View File

@ -22,6 +22,7 @@
#pragma once
#include <libyul/optimiser/OptimiserStep.h>
#include <libyul/backends/evm/StackLayoutGenerator.h>
namespace solidity::yul
{
@ -61,6 +62,25 @@ public:
Object& _object,
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
);
};
}

View File

@ -95,6 +95,12 @@ void OptimiserSuite::run(
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;
reservedIdentifiers += _dialect.fixedFunctionNames();
@ -121,24 +127,32 @@ void OptimiserSuite::run(
// We ignore the return value because we will get a much better error
// message once we perform code generation.
StackCompressor::run(
_dialect,
_object,
_optimizeStackAllocation,
stackCompressorMaxIterations
);
if (!usesOptimizedCodeGenerator)
StackCompressor::run(
_dialect,
_object,
_optimizeStackAllocation,
stackCompressorMaxIterations
);
suite.runSequence("fDnTOc g", ast);
if (EVMDialect const* dialect = dynamic_cast<EVMDialect const*>(&_dialect))
if (evmDialect)
{
yulAssert(_meter, "");
ConstantOptimiser{*dialect, *_meter}(ast);
if (dialect->providesObjectAccess() && _optimizeStackAllocation)
StackLimitEvader::run(suite.m_context, _object, CompilabilityChecker{
ConstantOptimiser{*evmDialect, *_meter}(ast);
if (usesOptimizedCodeGenerator)
{
StackCompressor::run(
_dialect,
_object,
_optimizeStackAllocation
}.unreachableVariables);
_optimizeStackAllocation,
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))
{