From 442666d1818b0c78bb011a68ef079ebd602e4fcd Mon Sep 17 00:00:00 2001 From: Daniel Kirchner Date: Thu, 12 Aug 2021 18:22:29 +0200 Subject: [PATCH] Allow stack limit evasion in system yul routines during old code generation. --- libevmasm/AssemblyItem.h | 3 + libsolidity/codegen/CompilerContext.cpp | 14 ++++- libsolidity/codegen/CompilerContext.h | 16 ++++- libsolidity/codegen/CompilerUtils.cpp | 2 +- libyul/optimiser/OptimiserStep.h | 3 + libyul/optimiser/StackLimitEvader.cpp | 63 +++++++++++-------- libyul/optimiser/StackToMemoryMover.cpp | 5 +- libyul/optimiser/Suite.cpp | 17 +++-- libyul/optimiser/Suite.h | 15 ++++- .../various/negative_stack_height.sol | 2 +- 10 files changed, 95 insertions(+), 45 deletions(-) diff --git a/libevmasm/AssemblyItem.h b/libevmasm/AssemblyItem.h index 5d810c02c..62f3e092a 100644 --- a/libevmasm/AssemblyItem.h +++ b/libevmasm/AssemblyItem.h @@ -63,6 +63,9 @@ public: AssemblyItem(u256 _push, langutil::SourceLocation _location = langutil::SourceLocation()): 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 _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()): m_type(Operation), m_instruction(_i), diff --git a/libsolidity/codegen/CompilerContext.cpp b/libsolidity/codegen/CompilerContext.cpp index 5503c3ecd..7d87fa08e 100644 --- a/libsolidity/codegen/CompilerContext.cpp +++ b/libsolidity/codegen/CompilerContext.cpp @@ -484,7 +484,7 @@ void CompilerContext::appendInlineAssembly( obj.code = parserResult; obj.analysisInfo = make_shared(analysisInfo); - optimizeYul(obj, dialect, _optimiserSettings, externallyUsedIdentifiers); + optimizeYul(obj, dialect, _optimiserSettings, externallyUsedIdentifiers, _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 const& _externalIdentifiers) +void CompilerContext::optimizeYul( + yul::Object& _object, + yul::EVMDialect const& _dialect, + OptimiserSettings const& _optimiserSettings, + std::set const& _externalIdentifiers, + bool _system +) { #ifdef SOL_OUTPUT_ASM 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; yul::GasMeter meter(_dialect, isCreation, _optimiserSettings.expectedExecutionsPerDeployment); + solAssert(m_freeMemoryInitPush, ""); yul::OptimiserSuite::run( _dialect, &meter, @@ -546,7 +553,8 @@ void CompilerContext::optimizeYul(yul::Object& _object, yul::EVMDialect const& _ _optimiserSettings.optimizeStackAllocation, _optimiserSettings.yulOptimiserSteps, isCreation? nullopt : make_optional(_optimiserSettings.expectedExecutionsPerDeployment), - _externalIdentifiers + _externalIdentifiers, + _system ? m_freeMemoryInitPush : shared_ptr{} ); #ifdef SOL_OUTPUT_ASM diff --git a/libsolidity/codegen/CompilerContext.h b/libsolidity/codegen/CompilerContext.h index b36397864..6cefe6231 100644 --- a/libsolidity/codegen/CompilerContext.h +++ b/libsolidity/codegen/CompilerContext.h @@ -276,7 +276,13 @@ public: /// Otherwise returns "revert(0, 0)". std::string revertReasonIfDebug(std::string const& _message = ""); - void optimizeYul(yul::Object& _object, yul::EVMDialect const& _dialect, OptimiserSettings const& _optimiserSetting, std::set const& _externalIdentifiers = {}); + void optimizeYul( + yul::Object& _object, + yul::EVMDialect const& _dialect, + OptimiserSettings const& _optimiserSetting, + std::set const& _externalIdentifiers = {}, + bool _system = false + ); /// Appends arbitrary data to the end of the bytecode. void appendToAuxiliaryData(bytes const& _data) { m_asm->appendToAuxiliaryData(_data); } @@ -309,6 +315,12 @@ public: RevertStrings revertStrings() const { return m_revertStrings; } + evmasm::AssemblyItem makeFreeMemoryInitPush(u256 _value) + { + m_freeMemoryInitPush = std::make_shared(_value); + return evmasm::AssemblyItem(m_freeMemoryInitPush); + } + private: /// Updates source location set in the assembly. void updateSourceLocation(); @@ -394,6 +406,8 @@ private: std::queue>> m_lowLevelFunctionGenerationQueue; /// Flag to check that appendYulUtilityFunctions() was called exactly once bool m_appendYulUtilityFunctionsRan = false; + /// The assembly item that pushes the initial value of the free memory pointer. + std::shared_ptr m_freeMemoryInitPush; }; } diff --git a/libsolidity/codegen/CompilerUtils.cpp b/libsolidity/codegen/CompilerUtils.cpp index 9ae1741dd..95234f849 100644 --- a/libsolidity/codegen/CompilerUtils.cpp +++ b/libsolidity/codegen/CompilerUtils.cpp @@ -55,7 +55,7 @@ void CompilerUtils::initialiseFreeMemoryPointer() { size_t reservedMemory = m_context.reservedMemory(); solAssert(bigint(generalPurposeMemoryStart) + bigint(reservedMemory) < bigint(1) << 63, ""); - m_context << (u256(generalPurposeMemoryStart) + reservedMemory); + m_context << m_context.makeFreeMemoryInitPush((u256(generalPurposeMemoryStart) + reservedMemory)); storeFreeMemoryPointer(); } diff --git a/libyul/optimiser/OptimiserStep.h b/libyul/optimiser/OptimiserStep.h index e3e5fe4c6..abc631bba 100644 --- a/libyul/optimiser/OptimiserStep.h +++ b/libyul/optimiser/OptimiserStep.h @@ -20,6 +20,8 @@ #include +#include + #include #include #include @@ -39,6 +41,7 @@ struct OptimiserStepContext std::set const& reservedIdentifiers; /// The value nullopt represents creation code std::optional expectedExecutionsPerDeployment; + std::shared_ptr externalFreeMemoryPointerInitializer{}; }; diff --git a/libyul/optimiser/StackLimitEvader.cpp b/libyul/optimiser/StackLimitEvader.cpp index 5cf1cd57f..e450a0105 100644 --- a/libyul/optimiser/StackLimitEvader.cpp +++ b/libyul/optimiser/StackLimitEvader.cpp @@ -124,10 +124,7 @@ void StackLimitEvader::run( ) { auto const* evmDialect = dynamic_cast(&_context.dialect); - yulAssert( - evmDialect && evmDialect->providesObjectAccess(), - "StackLimitEvader can only be run on objects using the EVMDialect with object access." - ); + yulAssert(evmDialect, "StackLimitEvader can only be run on objects using the EVMDialect."); if (evmDialect && evmDialect->evmVersion() > langutil::EVMVersion::homestead()) { yul::AsmAnalysisInfo analysisInfo = yul::AsmAnalyzer::analyzeStrictAssertCorrect(*evmDialect, _object); @@ -165,27 +162,31 @@ void StackLimitEvader::run( { yulAssert(_object.code, ""); auto const* evmDialect = dynamic_cast(&_context.dialect); - yulAssert( - evmDialect && evmDialect->providesObjectAccess(), - "StackLimitEvader can only be run on objects using the EVMDialect with object access." - ); + yulAssert(evmDialect, "StackLimitEvader can only be run on objects using the EVMDialect."); - vector memoryGuardCalls = FunctionCallFinder::run( - *_object.code, - "memoryguard"_yulstring - ); - // Do not optimise, if no ``memoryguard`` call is found. - if (memoryGuardCalls.empty()) - return; + u256 reservedMemory = 0; - // Make sure all calls to ``memoryguard`` we found have the same value as argument (otherwise, abort). - u256 reservedMemory = literalArgumentValue(*memoryGuardCalls.front()); - yulAssert(reservedMemory < u256(1) << 32 - 1, ""); - - for (FunctionCall const* memoryGuardCall: memoryGuardCalls) - if (reservedMemory != literalArgumentValue(*memoryGuardCall)) + if (_context.externalFreeMemoryPointerInitializer) + reservedMemory = *_context.externalFreeMemoryPointerInitializer; + else + { + vector memoryGuardCalls = FunctionCallFinder::run( + *_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). + 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); // We cannot move variables in recursive functions to fixed memory offsets. @@ -195,6 +196,11 @@ void StackLimitEvader::run( map 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}; uint64_t requiredSlots = memoryOffsetAllocator.run(); yulAssert(requiredSlots < (uint64_t(1) << 32) - 1, ""); @@ -202,10 +208,13 @@ void StackLimitEvader::run( StackToMemoryMover::run(_context, reservedMemory, memoryOffsetAllocator.slotAllocations, requiredSlots, *_object.code); reservedMemory += 32 * requiredSlots; - for (FunctionCall* memoryGuardCall: FunctionCallFinder::run(*_object.code, "memoryguard"_yulstring)) - { - Literal* literal = std::get_if(&memoryGuardCall->arguments.front()); - yulAssert(literal && literal->kind == LiteralKind::Number, ""); - literal->value = YulString{util::toCompactHexWithPrefix(reservedMemory)}; - } + if (_context.externalFreeMemoryPointerInitializer) + *_context.externalFreeMemoryPointerInitializer = reservedMemory; + else + for (FunctionCall* memoryGuardCall: FunctionCallFinder::run(*_object.code, "memoryguard"_yulstring)) + { + Literal* literal = std::get_if(&memoryGuardCall->arguments.front()); + yulAssert(literal && literal->kind == LiteralKind::Number, ""); + literal->value = YulString{util::toCompactHexWithPrefix(reservedMemory)}; + } } diff --git a/libyul/optimiser/StackToMemoryMover.cpp b/libyul/optimiser/StackToMemoryMover.cpp index 87976e116..0547bd261 100644 --- a/libyul/optimiser/StackToMemoryMover.cpp +++ b/libyul/optimiser/StackToMemoryMover.cpp @@ -109,10 +109,7 @@ m_nameDispenser(_context.dispenser), m_functionReturnVariables(move(_functionReturnVariables)) { auto const* evmDialect = dynamic_cast(&_context.dialect); - yulAssert( - evmDialect && evmDialect->providesObjectAccess(), - "StackToMemoryMover can only be run on objects using the EVMDialect with object access." - ); + yulAssert(evmDialect, "StackToMemoryMover can only be run on objects using the EVMDialect."); } void StackToMemoryMover::operator()(FunctionDefinition& _functionDefinition) diff --git a/libyul/optimiser/Suite.cpp b/libyul/optimiser/Suite.cpp index ee5e6e620..83ec8b6df 100644 --- a/libyul/optimiser/Suite.cpp +++ b/libyul/optimiser/Suite.cpp @@ -89,7 +89,8 @@ void OptimiserSuite::run( bool _optimizeStackAllocation, string const& _optimisationSequence, optional _expectedExecutionsPerDeployment, - set const& _externallyUsedIdentifiers + set const& _externallyUsedIdentifiers, + std::shared_ptr _externalFreeMemoryPointerInitializer ) { EVMDialect const* evmDialect = dynamic_cast(&_dialect); @@ -107,7 +108,14 @@ void OptimiserSuite::run( )(*_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 // ForLoopInitRewriter. Run them first to be able to run arbitrary sequences safely. @@ -144,10 +152,9 @@ void OptimiserSuite::run( _optimizeStackAllocation, 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); } else if (dynamic_cast(&_dialect)) diff --git a/libyul/optimiser/Suite.h b/libyul/optimiser/Suite.h index fa7b06bd0..817ffe755 100644 --- a/libyul/optimiser/Suite.h +++ b/libyul/optimiser/Suite.h @@ -66,7 +66,8 @@ public: bool _optimizeStackAllocation, std::string const& _optimisationSequence, std::optional _expectedExecutionsPerDeployment, - std::set const& _externallyUsedIdentifiers = {} + std::set const& _externallyUsedIdentifiers = {}, + std::shared_ptr _externalFreeMemoryPointerInitializer = {} ); /// Ensures that specified sequence of step abbreviations is well-formed and can be executed. @@ -91,10 +92,18 @@ private: std::set const& _externallyUsedIdentifiers, Debug _debug, Block& _ast, - std::optional expectedExecutionsPerDeployment + std::optional expectedExecutionsPerDeployment, + std::shared_ptr _externalFreeMemoryPointerInitializer = {} + ): m_dispenser{_dialect, _ast, _externallyUsedIdentifiers}, - m_context{_dialect, m_dispenser, _externallyUsedIdentifiers, expectedExecutionsPerDeployment}, + m_context{ + _dialect, + m_dispenser, + _externallyUsedIdentifiers, + expectedExecutionsPerDeployment, + _externalFreeMemoryPointerInitializer + }, m_debug(_debug) {} diff --git a/test/libsolidity/semanticTests/various/negative_stack_height.sol b/test/libsolidity/semanticTests/various/negative_stack_height.sol index ba1bebc18..4a97fff91 100644 --- a/test/libsolidity/semanticTests/various/negative_stack_height.sol +++ b/test/libsolidity/semanticTests/various/negative_stack_height.sol @@ -62,6 +62,6 @@ contract C { // via yul disabled because of stack issues. // ==== -// compileViaYul: false +// compileViaYul: also // ---- // constructor() ->