Allow stack limit evasion in system yul routines during old code generation.

This commit is contained in:
Daniel Kirchner 2021-08-12 18:22:29 +02:00
parent 8bd358074e
commit 442666d181
10 changed files with 95 additions and 45 deletions

View File

@ -63,6 +63,9 @@ public:
AssemblyItem(u256 _push, langutil::SourceLocation _location = langutil::SourceLocation()): AssemblyItem(u256 _push, langutil::SourceLocation _location = langutil::SourceLocation()):
AssemblyItem(Push, std::move(_push), std::move(_location)) { } AssemblyItem(Push, std::move(_push), std::move(_location)) { }
/// Used only for the free memory pointer as "memoryguard". Should probably be replaced by a separate AssemblyItemType.
AssemblyItem(std::shared_ptr<u256> _pushData, langutil::SourceLocation _location = langutil::SourceLocation()):
m_type(Push), m_data(std::move(_pushData)), m_location(std::move(_location)) { }
AssemblyItem(Instruction _i, langutil::SourceLocation _location = langutil::SourceLocation()): AssemblyItem(Instruction _i, langutil::SourceLocation _location = langutil::SourceLocation()):
m_type(Operation), m_type(Operation),
m_instruction(_i), m_instruction(_i),

View File

@ -484,7 +484,7 @@ void CompilerContext::appendInlineAssembly(
obj.code = parserResult; obj.code = parserResult;
obj.analysisInfo = make_shared<yul::AsmAnalysisInfo>(analysisInfo); obj.analysisInfo = make_shared<yul::AsmAnalysisInfo>(analysisInfo);
optimizeYul(obj, dialect, _optimiserSettings, externallyUsedIdentifiers); optimizeYul(obj, dialect, _optimiserSettings, externallyUsedIdentifiers, _system);
if (_system) if (_system)
{ {
@ -531,7 +531,13 @@ void CompilerContext::appendInlineAssembly(
} }
void CompilerContext::optimizeYul(yul::Object& _object, yul::EVMDialect const& _dialect, OptimiserSettings const& _optimiserSettings, std::set<yul::YulString> const& _externalIdentifiers) void CompilerContext::optimizeYul(
yul::Object& _object,
yul::EVMDialect const& _dialect,
OptimiserSettings const& _optimiserSettings,
std::set<yul::YulString> const& _externalIdentifiers,
bool _system
)
{ {
#ifdef SOL_OUTPUT_ASM #ifdef SOL_OUTPUT_ASM
cout << yul::AsmPrinter(*dialect)(*_object.code) << endl; cout << yul::AsmPrinter(*dialect)(*_object.code) << endl;
@ -539,6 +545,7 @@ void CompilerContext::optimizeYul(yul::Object& _object, yul::EVMDialect const& _
bool const isCreation = runtimeContext() != nullptr; bool const isCreation = runtimeContext() != nullptr;
yul::GasMeter meter(_dialect, isCreation, _optimiserSettings.expectedExecutionsPerDeployment); yul::GasMeter meter(_dialect, isCreation, _optimiserSettings.expectedExecutionsPerDeployment);
solAssert(m_freeMemoryInitPush, "");
yul::OptimiserSuite::run( yul::OptimiserSuite::run(
_dialect, _dialect,
&meter, &meter,
@ -546,7 +553,8 @@ void CompilerContext::optimizeYul(yul::Object& _object, yul::EVMDialect const& _
_optimiserSettings.optimizeStackAllocation, _optimiserSettings.optimizeStackAllocation,
_optimiserSettings.yulOptimiserSteps, _optimiserSettings.yulOptimiserSteps,
isCreation? nullopt : make_optional(_optimiserSettings.expectedExecutionsPerDeployment), isCreation? nullopt : make_optional(_optimiserSettings.expectedExecutionsPerDeployment),
_externalIdentifiers _externalIdentifiers,
_system ? m_freeMemoryInitPush : shared_ptr<u256>{}
); );
#ifdef SOL_OUTPUT_ASM #ifdef SOL_OUTPUT_ASM

View File

@ -276,7 +276,13 @@ public:
/// Otherwise returns "revert(0, 0)". /// Otherwise returns "revert(0, 0)".
std::string revertReasonIfDebug(std::string const& _message = ""); std::string revertReasonIfDebug(std::string const& _message = "");
void optimizeYul(yul::Object& _object, yul::EVMDialect const& _dialect, OptimiserSettings const& _optimiserSetting, std::set<yul::YulString> const& _externalIdentifiers = {}); void optimizeYul(
yul::Object& _object,
yul::EVMDialect const& _dialect,
OptimiserSettings const& _optimiserSetting,
std::set<yul::YulString> const& _externalIdentifiers = {},
bool _system = false
);
/// Appends arbitrary data to the end of the bytecode. /// Appends arbitrary data to the end of the bytecode.
void appendToAuxiliaryData(bytes const& _data) { m_asm->appendToAuxiliaryData(_data); } void appendToAuxiliaryData(bytes const& _data) { m_asm->appendToAuxiliaryData(_data); }
@ -309,6 +315,12 @@ public:
RevertStrings revertStrings() const { return m_revertStrings; } RevertStrings revertStrings() const { return m_revertStrings; }
evmasm::AssemblyItem makeFreeMemoryInitPush(u256 _value)
{
m_freeMemoryInitPush = std::make_shared<u256>(_value);
return evmasm::AssemblyItem(m_freeMemoryInitPush);
}
private: private:
/// Updates source location set in the assembly. /// Updates source location set in the assembly.
void updateSourceLocation(); void updateSourceLocation();
@ -394,6 +406,8 @@ private:
std::queue<std::tuple<std::string, unsigned, unsigned, std::function<void(CompilerContext&)>>> m_lowLevelFunctionGenerationQueue; std::queue<std::tuple<std::string, unsigned, unsigned, std::function<void(CompilerContext&)>>> m_lowLevelFunctionGenerationQueue;
/// Flag to check that appendYulUtilityFunctions() was called exactly once /// Flag to check that appendYulUtilityFunctions() was called exactly once
bool m_appendYulUtilityFunctionsRan = false; bool m_appendYulUtilityFunctionsRan = false;
/// The assembly item that pushes the initial value of the free memory pointer.
std::shared_ptr<u256> m_freeMemoryInitPush;
}; };
} }

View File

@ -55,7 +55,7 @@ void CompilerUtils::initialiseFreeMemoryPointer()
{ {
size_t reservedMemory = m_context.reservedMemory(); size_t reservedMemory = m_context.reservedMemory();
solAssert(bigint(generalPurposeMemoryStart) + bigint(reservedMemory) < bigint(1) << 63, ""); solAssert(bigint(generalPurposeMemoryStart) + bigint(reservedMemory) < bigint(1) << 63, "");
m_context << (u256(generalPurposeMemoryStart) + reservedMemory); m_context << m_context.makeFreeMemoryInitPush((u256(generalPurposeMemoryStart) + reservedMemory));
storeFreeMemoryPointer(); storeFreeMemoryPointer();
} }

View File

@ -20,6 +20,8 @@
#include <libyul/Exceptions.h> #include <libyul/Exceptions.h>
#include <libsolutil/Common.h>
#include <optional> #include <optional>
#include <string> #include <string>
#include <set> #include <set>
@ -39,6 +41,7 @@ struct OptimiserStepContext
std::set<YulString> const& reservedIdentifiers; std::set<YulString> const& reservedIdentifiers;
/// The value nullopt represents creation code /// The value nullopt represents creation code
std::optional<size_t> expectedExecutionsPerDeployment; std::optional<size_t> expectedExecutionsPerDeployment;
std::shared_ptr<u256> externalFreeMemoryPointerInitializer{};
}; };

View File

@ -124,10 +124,7 @@ void StackLimitEvader::run(
) )
{ {
auto const* evmDialect = dynamic_cast<EVMDialect const*>(&_context.dialect); auto const* evmDialect = dynamic_cast<EVMDialect const*>(&_context.dialect);
yulAssert( yulAssert(evmDialect, "StackLimitEvader can only be run on objects using the EVMDialect.");
evmDialect && evmDialect->providesObjectAccess(),
"StackLimitEvader can only be run on objects using the EVMDialect with object access."
);
if (evmDialect && evmDialect->evmVersion() > langutil::EVMVersion::homestead()) if (evmDialect && evmDialect->evmVersion() > langutil::EVMVersion::homestead())
{ {
yul::AsmAnalysisInfo analysisInfo = yul::AsmAnalyzer::analyzeStrictAssertCorrect(*evmDialect, _object); yul::AsmAnalysisInfo analysisInfo = yul::AsmAnalyzer::analyzeStrictAssertCorrect(*evmDialect, _object);
@ -165,27 +162,31 @@ void StackLimitEvader::run(
{ {
yulAssert(_object.code, ""); yulAssert(_object.code, "");
auto const* evmDialect = dynamic_cast<EVMDialect const*>(&_context.dialect); auto const* evmDialect = dynamic_cast<EVMDialect const*>(&_context.dialect);
yulAssert( yulAssert(evmDialect, "StackLimitEvader can only be run on objects using the EVMDialect.");
evmDialect && evmDialect->providesObjectAccess(),
"StackLimitEvader can only be run on objects using the EVMDialect with object access."
);
vector<FunctionCall*> memoryGuardCalls = FunctionCallFinder::run( u256 reservedMemory = 0;
*_object.code,
"memoryguard"_yulstring
);
// Do not optimise, if no ``memoryguard`` call is found.
if (memoryGuardCalls.empty())
return;
// Make sure all calls to ``memoryguard`` we found have the same value as argument (otherwise, abort). if (_context.externalFreeMemoryPointerInitializer)
u256 reservedMemory = literalArgumentValue(*memoryGuardCalls.front()); reservedMemory = *_context.externalFreeMemoryPointerInitializer;
yulAssert(reservedMemory < u256(1) << 32 - 1, ""); else
{
for (FunctionCall const* memoryGuardCall: memoryGuardCalls) vector<FunctionCall*> memoryGuardCalls = FunctionCallFinder::run(
if (reservedMemory != literalArgumentValue(*memoryGuardCall)) *_object.code,
"memoryguard"_yulstring
);
// Do not optimise, if no ``memoryguard`` call is found.
if (memoryGuardCalls.empty())
return; return;
// Make sure all calls to ``memoryguard`` we found have the same value as argument (otherwise, abort).
reservedMemory = literalArgumentValue(*memoryGuardCalls.front());
yulAssert(reservedMemory < u256(1) << 32 - 1, "");
for (FunctionCall const* memoryGuardCall: memoryGuardCalls)
if (reservedMemory != literalArgumentValue(*memoryGuardCall))
return;
}
CallGraph callGraph = CallGraphGenerator::callGraph(*_object.code); CallGraph callGraph = CallGraphGenerator::callGraph(*_object.code);
// We cannot move variables in recursive functions to fixed memory offsets. // We cannot move variables in recursive functions to fixed memory offsets.
@ -195,6 +196,11 @@ void StackLimitEvader::run(
map<YulString, FunctionDefinition const*> functionDefinitions = FunctionDefinitionCollector::run(*_object.code); map<YulString, FunctionDefinition const*> functionDefinitions = FunctionDefinitionCollector::run(*_object.code);
if (_context.externalFreeMemoryPointerInitializer)
// Simulate calls from outer block to all defined functions.
for (auto functionDef: functionDefinitions)
callGraph.functionCalls[YulString{}].insert(functionDef.first);
MemoryOffsetAllocator memoryOffsetAllocator{_unreachableVariables, callGraph.functionCalls, functionDefinitions}; MemoryOffsetAllocator memoryOffsetAllocator{_unreachableVariables, callGraph.functionCalls, functionDefinitions};
uint64_t requiredSlots = memoryOffsetAllocator.run(); uint64_t requiredSlots = memoryOffsetAllocator.run();
yulAssert(requiredSlots < (uint64_t(1) << 32) - 1, ""); yulAssert(requiredSlots < (uint64_t(1) << 32) - 1, "");
@ -202,10 +208,13 @@ void StackLimitEvader::run(
StackToMemoryMover::run(_context, reservedMemory, memoryOffsetAllocator.slotAllocations, requiredSlots, *_object.code); StackToMemoryMover::run(_context, reservedMemory, memoryOffsetAllocator.slotAllocations, requiredSlots, *_object.code);
reservedMemory += 32 * requiredSlots; reservedMemory += 32 * requiredSlots;
for (FunctionCall* memoryGuardCall: FunctionCallFinder::run(*_object.code, "memoryguard"_yulstring)) if (_context.externalFreeMemoryPointerInitializer)
{ *_context.externalFreeMemoryPointerInitializer = reservedMemory;
Literal* literal = std::get_if<Literal>(&memoryGuardCall->arguments.front()); else
yulAssert(literal && literal->kind == LiteralKind::Number, ""); for (FunctionCall* memoryGuardCall: FunctionCallFinder::run(*_object.code, "memoryguard"_yulstring))
literal->value = YulString{util::toCompactHexWithPrefix(reservedMemory)}; {
} Literal* literal = std::get_if<Literal>(&memoryGuardCall->arguments.front());
yulAssert(literal && literal->kind == LiteralKind::Number, "");
literal->value = YulString{util::toCompactHexWithPrefix(reservedMemory)};
}
} }

View File

@ -109,10 +109,7 @@ m_nameDispenser(_context.dispenser),
m_functionReturnVariables(move(_functionReturnVariables)) m_functionReturnVariables(move(_functionReturnVariables))
{ {
auto const* evmDialect = dynamic_cast<EVMDialect const*>(&_context.dialect); auto const* evmDialect = dynamic_cast<EVMDialect const*>(&_context.dialect);
yulAssert( yulAssert(evmDialect, "StackToMemoryMover can only be run on objects using the EVMDialect.");
evmDialect && evmDialect->providesObjectAccess(),
"StackToMemoryMover can only be run on objects using the EVMDialect with object access."
);
} }
void StackToMemoryMover::operator()(FunctionDefinition& _functionDefinition) void StackToMemoryMover::operator()(FunctionDefinition& _functionDefinition)

View File

@ -89,7 +89,8 @@ void OptimiserSuite::run(
bool _optimizeStackAllocation, bool _optimizeStackAllocation,
string const& _optimisationSequence, string const& _optimisationSequence,
optional<size_t> _expectedExecutionsPerDeployment, optional<size_t> _expectedExecutionsPerDeployment,
set<YulString> const& _externallyUsedIdentifiers set<YulString> const& _externallyUsedIdentifiers,
std::shared_ptr<u256> _externalFreeMemoryPointerInitializer
) )
{ {
EVMDialect const* evmDialect = dynamic_cast<EVMDialect const*>(&_dialect); EVMDialect const* evmDialect = dynamic_cast<EVMDialect const*>(&_dialect);
@ -107,7 +108,14 @@ void OptimiserSuite::run(
)(*_object.code)); )(*_object.code));
Block& ast = *_object.code; Block& ast = *_object.code;
OptimiserSuite suite(_dialect, reservedIdentifiers, Debug::None, ast, _expectedExecutionsPerDeployment); OptimiserSuite suite(
_dialect,
reservedIdentifiers,
Debug::None,
ast,
_expectedExecutionsPerDeployment,
_externalFreeMemoryPointerInitializer
);
// Some steps depend on properties ensured by FunctionHoister, BlockFlattener, FunctionGrouper and // Some steps depend on properties ensured by FunctionHoister, BlockFlattener, FunctionGrouper and
// ForLoopInitRewriter. Run them first to be able to run arbitrary sequences safely. // ForLoopInitRewriter. Run them first to be able to run arbitrary sequences safely.
@ -144,10 +152,9 @@ void OptimiserSuite::run(
_optimizeStackAllocation, _optimizeStackAllocation,
stackCompressorMaxIterations stackCompressorMaxIterations
); );
if (evmDialect->providesObjectAccess()) StackLimitEvader::run(suite.m_context, _object);
StackLimitEvader::run(suite.m_context, _object);
} }
else if (evmDialect->providesObjectAccess() && _optimizeStackAllocation) else if (_optimizeStackAllocation)
StackLimitEvader::run(suite.m_context, _object); StackLimitEvader::run(suite.m_context, _object);
} }
else if (dynamic_cast<WasmDialect const*>(&_dialect)) else if (dynamic_cast<WasmDialect const*>(&_dialect))

View File

@ -66,7 +66,8 @@ public:
bool _optimizeStackAllocation, bool _optimizeStackAllocation,
std::string const& _optimisationSequence, std::string const& _optimisationSequence,
std::optional<size_t> _expectedExecutionsPerDeployment, std::optional<size_t> _expectedExecutionsPerDeployment,
std::set<YulString> const& _externallyUsedIdentifiers = {} std::set<YulString> const& _externallyUsedIdentifiers = {},
std::shared_ptr<u256> _externalFreeMemoryPointerInitializer = {}
); );
/// Ensures that specified sequence of step abbreviations is well-formed and can be executed. /// Ensures that specified sequence of step abbreviations is well-formed and can be executed.
@ -91,10 +92,18 @@ private:
std::set<YulString> const& _externallyUsedIdentifiers, std::set<YulString> const& _externallyUsedIdentifiers,
Debug _debug, Debug _debug,
Block& _ast, Block& _ast,
std::optional<size_t> expectedExecutionsPerDeployment std::optional<size_t> expectedExecutionsPerDeployment,
std::shared_ptr<u256> _externalFreeMemoryPointerInitializer = {}
): ):
m_dispenser{_dialect, _ast, _externallyUsedIdentifiers}, m_dispenser{_dialect, _ast, _externallyUsedIdentifiers},
m_context{_dialect, m_dispenser, _externallyUsedIdentifiers, expectedExecutionsPerDeployment}, m_context{
_dialect,
m_dispenser,
_externallyUsedIdentifiers,
expectedExecutionsPerDeployment,
_externalFreeMemoryPointerInitializer
},
m_debug(_debug) m_debug(_debug)
{} {}

View File

@ -62,6 +62,6 @@ contract C {
// via yul disabled because of stack issues. // via yul disabled because of stack issues.
// ==== // ====
// compileViaYul: false // compileViaYul: also
// ---- // ----
// constructor() -> // constructor() ->