diff --git a/Changelog.md b/Changelog.md index 99177776e..7462e2bde 100644 --- a/Changelog.md +++ b/Changelog.md @@ -8,6 +8,7 @@ Compiler Features: * Commandline Interface: Add `--no-cbor-metadata` that skips CBOR metadata from getting appended at the end of the bytecode. * Natspec: Add event Natspec inheritance for devdoc. * Standard JSON: Add a boolean field `settings.metadata.appendCBOR` that skips CBOR metadata from getting appended at the end of the bytecode. + * Yul EVM Code Transform: Generate more optimal code for user-defined functions that always terminate a transaction. No return labels will be pushed for calls to functions that always terminate. * Yul Optimizer: Allow replacing the previously hard-coded cleanup sequence by specifying custom steps after a colon delimiter (``:``) in the sequence string. * Language Server: Add basic document hover support. * Optimizer: Added optimization rule ``and(shl(X, Y), shl(X, Z)) => shl(X, and(Y, Z))``. diff --git a/libyul/backends/evm/ControlFlowGraph.h b/libyul/backends/evm/ControlFlowGraph.h index b267b8461..853a9e705 100644 --- a/libyul/backends/evm/ControlFlowGraph.h +++ b/libyul/backends/evm/ControlFlowGraph.h @@ -148,6 +148,8 @@ struct CFG /// True, if the call is recursive, i.e. entering the function involves a control flow path (potentially involving /// more intermediate function calls) that leads back to this very call. bool recursive = false; + /// True, if the call can return. + bool canContinue = true; }; struct Assignment { @@ -210,10 +212,12 @@ struct CFG { std::shared_ptr debugData; Scope::Function const& function; + FunctionDefinition const& functionDefinition; BasicBlock* entry = nullptr; std::vector parameters; std::vector returnVariables; std::vector exits; + bool canContinue = true; }; /// The main entry point, i.e. the start of the outermost Yul block. diff --git a/libyul/backends/evm/ControlFlowGraphBuilder.cpp b/libyul/backends/evm/ControlFlowGraphBuilder.cpp index 1980fb442..d08dcf16c 100644 --- a/libyul/backends/evm/ControlFlowGraphBuilder.cpp +++ b/libyul/backends/evm/ControlFlowGraphBuilder.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include @@ -214,7 +215,8 @@ std::unique_ptr ControlFlowGraphBuilder::build( auto result = std::make_unique(); result->entry = &result->makeBlock(debugDataOf(_block)); - ControlFlowGraphBuilder builder(*result, _analysisInfo, _dialect); + ControlFlowSideEffectsCollector sideEffects(_dialect, _block); + ControlFlowGraphBuilder builder(*result, _analysisInfo, sideEffects.functionSideEffects(), _dialect); builder.m_currentBlock = result->entry; builder(_block); @@ -232,10 +234,12 @@ std::unique_ptr ControlFlowGraphBuilder::build( ControlFlowGraphBuilder::ControlFlowGraphBuilder( CFG& _graph, AsmAnalysisInfo const& _analysisInfo, + map const& _functionSideEffects, Dialect const& _dialect ): m_graph(_graph), m_info(_analysisInfo), + m_functionSideEffects(_functionSideEffects), m_dialect(_dialect) { } @@ -285,10 +289,10 @@ void ControlFlowGraphBuilder::operator()(Assignment const& _assignment) return VariableSlot{lookupVariable(_var.name), _var.debugData}; }) | ranges::to>; - yulAssert(m_currentBlock, ""); + Stack input = visitAssignmentRightHandSide(*_assignment.value, assignedVariables.size()); + yulAssert(m_currentBlock); m_currentBlock->operations.emplace_back(CFG::Operation{ - // input - visitAssignmentRightHandSide(*_assignment.value, assignedVariables.size()), + std::move(input), // output assignedVariables | ranges::to, // operation @@ -297,7 +301,6 @@ void ControlFlowGraphBuilder::operator()(Assignment const& _assignment) } void ControlFlowGraphBuilder::operator()(ExpressionStatement const& _exprStmt) { - yulAssert(m_currentBlock, ""); std::visit(util::GenericVisitor{ [&](FunctionCall const& _call) { Stack const& output = visitFunctionCall(_call); @@ -305,16 +308,6 @@ void ControlFlowGraphBuilder::operator()(ExpressionStatement const& _exprStmt) }, [&](auto const&) { yulAssert(false, ""); } }, _exprStmt.expression); - - // TODO: Ideally this would be done on the expression label and for all functions that always revert, - // not only for builtins. - if (auto const* funCall = get_if(&_exprStmt.expression)) - if (BuiltinFunction const* builtin = m_dialect.builtin(funCall->functionName.name)) - if (builtin->controlFlowSideEffects.terminatesOrReverts()) - { - m_currentBlock->exit = CFG::BasicBlock::Terminated{}; - m_currentBlock = &m_graph.makeBlock(debugDataOf(*m_currentBlock)); - } } void ControlFlowGraphBuilder::operator()(Block const& _block) @@ -331,7 +324,8 @@ void ControlFlowGraphBuilder::operator()(If const& _if) { auto& ifBranch = m_graph.makeBlock(debugDataOf(_if.body)); auto& afterIf = m_graph.makeBlock(debugDataOf(*m_currentBlock)); - makeConditionalJump(debugDataOf(_if), std::visit(*this, *_if.condition), ifBranch, afterIf); + StackSlot condition = std::visit(*this, *_if.condition); + makeConditionalJump(debugDataOf(_if), std::move(condition), ifBranch, afterIf); m_currentBlock = &ifBranch; (*this)(_if.body); jump(debugDataOf(_if.body), afterIf); @@ -349,8 +343,9 @@ void ControlFlowGraphBuilder::operator()(Switch const& _switch) // Artificially generate: // let := VariableSlot ghostVarSlot{ghostVar, debugDataOf(*_switch.expression)}; + StackSlot expression = std::visit(*this, *_switch.expression); m_currentBlock->operations.emplace_back(CFG::Operation{ - Stack{std::visit(*this, *_switch.expression)}, + Stack{std::move(expression)}, Stack{ghostVarSlot}, CFG::Assignment{_switch.debugData, {ghostVarSlot}} }); @@ -430,7 +425,8 @@ void ControlFlowGraphBuilder::operator()(ForLoop const& _loop) else { jump(debugDataOf(_loop.pre), loopCondition); - makeConditionalJump(debugDataOf(*_loop.condition), std::visit(*this, *_loop.condition), loopBody, afterLoop); + StackSlot condition = std::visit(*this, *_loop.condition); + makeConditionalJump(debugDataOf(*_loop.condition), std::move(condition), loopBody, afterLoop); m_currentBlock = &loopBody; (*this)(_loop.body); jump(debugDataOf(_loop.body), post); @@ -473,7 +469,7 @@ void ControlFlowGraphBuilder::operator()(FunctionDefinition const& _function) CFG::FunctionInfo& functionInfo = m_graph.functionInfo.at(&function); - ControlFlowGraphBuilder builder{m_graph, m_info, m_dialect}; + ControlFlowGraphBuilder builder{m_graph, m_info, m_functionSideEffects, m_dialect}; builder.m_currentFunction = &functionInfo; builder.m_currentBlock = functionInfo.entry; builder(_function.body); @@ -481,33 +477,35 @@ void ControlFlowGraphBuilder::operator()(FunctionDefinition const& _function) builder.m_currentBlock->exit = CFG::BasicBlock::FunctionReturn{debugDataOf(_function), &functionInfo}; } -void ControlFlowGraphBuilder::registerFunction(FunctionDefinition const& _function) +void ControlFlowGraphBuilder::registerFunction(FunctionDefinition const& _functionDefinition) { yulAssert(m_scope, ""); - yulAssert(m_scope->identifiers.count(_function.name), ""); - Scope::Function& function = std::get(m_scope->identifiers.at(_function.name)); + yulAssert(m_scope->identifiers.count(_functionDefinition.name), ""); + Scope::Function& function = std::get(m_scope->identifiers.at(_functionDefinition.name)); - yulAssert(m_info.scopes.at(&_function.body), ""); - Scope* virtualFunctionScope = m_info.scopes.at(m_info.virtualBlocks.at(&_function).get()).get(); + yulAssert(m_info.scopes.at(&_functionDefinition.body), ""); + Scope* virtualFunctionScope = m_info.scopes.at(m_info.virtualBlocks.at(&_functionDefinition).get()).get(); yulAssert(virtualFunctionScope, ""); bool inserted = m_graph.functionInfo.emplace(std::make_pair(&function, CFG::FunctionInfo{ - _function.debugData, + _functionDefinition.debugData, function, - &m_graph.makeBlock(debugDataOf(_function.body)), - _function.parameters | ranges::views::transform([&](auto const& _param) { + _functionDefinition, + &m_graph.makeBlock(debugDataOf(_functionDefinition.body)), + _functionDefinition.parameters | ranges::views::transform([&](auto const& _param) { return VariableSlot{ std::get(virtualFunctionScope->identifiers.at(_param.name)), _param.debugData }; }) | ranges::to, - _function.returnVariables | ranges::views::transform([&](auto const& _retVar) { + _functionDefinition.returnVariables | ranges::views::transform([&](auto const& _retVar) { return VariableSlot{ std::get(virtualFunctionScope->identifiers.at(_retVar.name)), _retVar.debugData }; }) | ranges::to, - {} + {}, + m_functionSideEffects.at(&_functionDefinition).canContinue })).second; yulAssert(inserted); } @@ -517,6 +515,8 @@ Stack const& ControlFlowGraphBuilder::visitFunctionCall(FunctionCall const& _cal yulAssert(m_scope, ""); yulAssert(m_currentBlock, ""); + Stack const* output = nullptr; + bool canContinue = true; if (BuiltinFunction const* builtin = m_dialect.builtin(_call.functionName.name)) { Stack inputs; @@ -524,7 +524,7 @@ Stack const& ControlFlowGraphBuilder::visitFunctionCall(FunctionCall const& _cal if (!builtin->literalArgument(idx).has_value()) inputs.emplace_back(std::visit(*this, arg)); CFG::BuiltinCall builtinCall{_call.debugData, *builtin, _call, inputs.size()}; - return m_currentBlock->operations.emplace_back(CFG::Operation{ + output = &m_currentBlock->operations.emplace_back(CFG::Operation{ // input std::move(inputs), // output @@ -534,14 +534,18 @@ Stack const& ControlFlowGraphBuilder::visitFunctionCall(FunctionCall const& _cal // operation std::move(builtinCall) }).output; + canContinue = builtin->controlFlowSideEffects.canContinue; } else { Scope::Function const& function = lookupFunction(_call.functionName.name); - Stack inputs{FunctionCallReturnLabelSlot{_call}}; + canContinue = m_graph.functionInfo.at(&function).canContinue; + Stack inputs; + if (canContinue) + inputs.emplace_back(FunctionCallReturnLabelSlot{_call}); for (auto const& arg: _call.arguments | ranges::views::reverse) inputs.emplace_back(std::visit(*this, arg)); - return m_currentBlock->operations.emplace_back(CFG::Operation{ + output = &m_currentBlock->operations.emplace_back(CFG::Operation{ // input std::move(inputs), // output @@ -549,9 +553,15 @@ Stack const& ControlFlowGraphBuilder::visitFunctionCall(FunctionCall const& _cal return TemporarySlot{_call, _i}; }) | ranges::to, // operation - CFG::FunctionCall{_call.debugData, function, _call} + CFG::FunctionCall{_call.debugData, function, _call, /* recursive */ false, canContinue} }).output; } + if (!canContinue) + { + m_currentBlock->exit = CFG::BasicBlock::Terminated{}; + m_currentBlock = &m_graph.makeBlock(debugDataOf(*m_currentBlock)); + } + return *output; } Stack ControlFlowGraphBuilder::visitAssignmentRightHandSide(Expression const& _expression, size_t _expectedSlotCount) diff --git a/libyul/backends/evm/ControlFlowGraphBuilder.h b/libyul/backends/evm/ControlFlowGraphBuilder.h index 2a99cfe5b..ffe935b0d 100644 --- a/libyul/backends/evm/ControlFlowGraphBuilder.h +++ b/libyul/backends/evm/ControlFlowGraphBuilder.h @@ -21,6 +21,7 @@ #pragma once #include +#include namespace solidity::yul { @@ -55,6 +56,7 @@ private: ControlFlowGraphBuilder( CFG& _graph, AsmAnalysisInfo const& _analysisInfo, + std::map const& _functionSideEffects, Dialect const& _dialect ); void registerFunction(FunctionDefinition const& _function); @@ -77,6 +79,7 @@ private: ); CFG& m_graph; AsmAnalysisInfo const& m_info; + std::map const& m_functionSideEffects; Dialect const& m_dialect; CFG::BasicBlock* m_currentBlock = nullptr; Scope* m_scope = nullptr; diff --git a/libyul/backends/evm/OptimizedEVMCodeTransform.cpp b/libyul/backends/evm/OptimizedEVMCodeTransform.cpp index 2fa99ccb2..fd3a572e0 100644 --- a/libyul/backends/evm/OptimizedEVMCodeTransform.cpp +++ b/libyul/backends/evm/OptimizedEVMCodeTransform.cpp @@ -71,7 +71,7 @@ void OptimizedEVMCodeTransform::operator()(CFG::FunctionCall const& _call) // Validate stack. { yulAssert(m_assembly.stackHeight() == static_cast(m_stack.size()), ""); - yulAssert(m_stack.size() >= _call.function.get().arguments.size() + 1, ""); + yulAssert(m_stack.size() >= _call.function.get().arguments.size() + (_call.canContinue ? 1 : 0), ""); // Assert that we got the correct arguments on stack for the call. for (auto&& [arg, slot]: ranges::zip_view( _call.functionCall.get().arguments | ranges::views::reverse, @@ -79,10 +79,13 @@ void OptimizedEVMCodeTransform::operator()(CFG::FunctionCall const& _call) )) validateSlot(slot, arg); // Assert that we got the correct return label on stack. - auto const* returnLabelSlot = get_if( - &m_stack.at(m_stack.size() - _call.functionCall.get().arguments.size() - 1) - ); - yulAssert(returnLabelSlot && &returnLabelSlot->call.get() == &_call.functionCall.get(), ""); + if (_call.canContinue) + { + auto const* returnLabelSlot = get_if( + &m_stack.at(m_stack.size() - _call.functionCall.get().arguments.size() - 1) + ); + yulAssert(returnLabelSlot && &returnLabelSlot->call.get() == &_call.functionCall.get(), ""); + } } // Emit code. @@ -90,16 +93,17 @@ void OptimizedEVMCodeTransform::operator()(CFG::FunctionCall const& _call) m_assembly.setSourceLocation(originLocationOf(_call)); m_assembly.appendJumpTo( getFunctionLabel(_call.function), - static_cast(_call.function.get().returns.size() - _call.function.get().arguments.size()) - 1, + static_cast(_call.function.get().returns.size() - _call.function.get().arguments.size()) - (_call.canContinue ? 1 : 0), AbstractAssembly::JumpType::IntoFunction ); - m_assembly.appendLabel(m_returnLabels.at(&_call.functionCall.get())); + if (_call.canContinue) + m_assembly.appendLabel(m_returnLabels.at(&_call.functionCall.get())); } // Update stack. { // Remove arguments and return label from m_stack. - for (size_t i = 0; i < _call.function.get().arguments.size() + 1; ++i) + for (size_t i = 0; i < _call.function.get().arguments.size() + (_call.canContinue ? 1 : 0); ++i) m_stack.pop_back(); // Push return values to m_stack. for (size_t index: ranges::views::iota(0u, _call.function.get().returns.size())) @@ -479,8 +483,9 @@ void OptimizedEVMCodeTransform::operator()(CFG::BasicBlock const& _block) }, [&](CFG::BasicBlock::FunctionReturn const& _functionReturn) { - yulAssert(m_currentFunctionInfo, ""); - yulAssert(m_currentFunctionInfo == _functionReturn.info, ""); + yulAssert(m_currentFunctionInfo); + yulAssert(m_currentFunctionInfo == _functionReturn.info); + yulAssert(m_currentFunctionInfo->canContinue); // Construct the function return layout, which is fully determined by the function signature. Stack exitStack = m_currentFunctionInfo->returnVariables | ranges::views::transform([](auto const& _varSlot){ @@ -494,11 +499,13 @@ void OptimizedEVMCodeTransform::operator()(CFG::BasicBlock const& _block) }, [&](CFG::BasicBlock::Terminated const&) { - // Assert that the last builtin call was in fact terminating. - yulAssert(!_block.operations.empty(), ""); - CFG::BuiltinCall const* builtinCall = get_if(&_block.operations.back().operation); - yulAssert(builtinCall, ""); - yulAssert(builtinCall->builtin.get().controlFlowSideEffects.terminatesOrReverts(), ""); + yulAssert(!_block.operations.empty()); + if (CFG::BuiltinCall const* builtinCall = get_if(&_block.operations.back().operation)) + yulAssert(builtinCall->builtin.get().controlFlowSideEffects.terminatesOrReverts(), ""); + else if (CFG::FunctionCall const* functionCall = get_if(&_block.operations.back().operation)) + yulAssert(!functionCall->canContinue); + else + yulAssert(false); } }, _block.exit); // TODO: We could assert that the last emitted assembly item terminated or was an (unconditional) jump. @@ -515,7 +522,8 @@ void OptimizedEVMCodeTransform::operator()(CFG::FunctionInfo const& _functionInf yulAssert(m_stack.empty() && m_assembly.stackHeight() == 0, ""); // Create function entry layout in m_stack. - m_stack.emplace_back(FunctionReturnLabelSlot{_functionInfo.function}); + if (_functionInfo.canContinue) + m_stack.emplace_back(FunctionReturnLabelSlot{_functionInfo.function}); for (auto const& param: _functionInfo.parameters | ranges::views::reverse) m_stack.emplace_back(param); m_assembly.setStackHeight(static_cast(m_stack.size())); diff --git a/libyul/backends/evm/StackLayoutGenerator.cpp b/libyul/backends/evm/StackLayoutGenerator.cpp index 8b39b364f..95c840733 100644 --- a/libyul/backends/evm/StackLayoutGenerator.cpp +++ b/libyul/backends/evm/StackLayoutGenerator.cpp @@ -54,7 +54,7 @@ StackLayout StackLayoutGenerator::run(CFG const& _cfg) StackLayoutGenerator{stackLayout}.processEntryPoint(*_cfg.entry); for (auto& functionInfo: _cfg.functionInfo | ranges::views::values) - StackLayoutGenerator{stackLayout}.processEntryPoint(*functionInfo.entry); + StackLayoutGenerator{stackLayout}.processEntryPoint(*functionInfo.entry, &functionInfo); return stackLayout; } @@ -331,7 +331,7 @@ Stack StackLayoutGenerator::propagateStackThroughBlock(Stack _exitStack, CFG::Ba return stack; } -void StackLayoutGenerator::processEntryPoint(CFG::BasicBlock const& _entry) +void StackLayoutGenerator::processEntryPoint(CFG::BasicBlock const& _entry, CFG::FunctionInfo const* _functionInfo) { list toVisit{&_entry}; set visited; @@ -402,7 +402,7 @@ void StackLayoutGenerator::processEntryPoint(CFG::BasicBlock const& _entry) } stitchConditionalJumps(_entry); - fillInJunk(_entry); + fillInJunk(_entry, _functionInfo); } optional StackLayoutGenerator::getExitLayoutOrStageDependencies( @@ -707,7 +707,7 @@ Stack StackLayoutGenerator::compressStack(Stack _stack) return _stack; } -void StackLayoutGenerator::fillInJunk(CFG::BasicBlock const& _block) +void StackLayoutGenerator::fillInJunk(CFG::BasicBlock const& _block, CFG::FunctionInfo const* _functionInfo) { /// Recursively adds junk to the subgraph starting on @a _entry. /// Since it is only called on cut-vertices, the full subgraph retains proper stack balance. @@ -767,10 +767,56 @@ void StackLayoutGenerator::fillInJunk(CFG::BasicBlock const& _block) createStackLayout(_source, _target, swap, dupOrPush, pop); return opGas; }; + /// @returns the number of junk slots to be prepended to @a _targetLayout for an optimal transition from + /// @a _entryLayout to @a _targetLayout. + auto getBestNumJunk = [&](Stack const& _entryLayout, Stack const& _targetLayout) -> size_t { + size_t bestCost = evaluateTransform(_entryLayout, _targetLayout); + size_t bestNumJunk = 0; + size_t maxJunk = _entryLayout.size(); + for (size_t numJunk = 1; numJunk <= maxJunk; ++numJunk) + { + size_t cost = evaluateTransform(_entryLayout, Stack{numJunk, JunkSlot{}} + _targetLayout); + if (cost < bestCost) + { + bestCost = cost; + bestNumJunk = numJunk; + } + } + return bestNumJunk; + }; + + if (_functionInfo && !_functionInfo->canContinue && _block.allowsJunk()) + { + size_t bestNumJunk = getBestNumJunk( + _functionInfo->parameters | ranges::views::reverse | ranges::to, + m_layout.blockInfos.at(&_block).entryLayout + ); + if (bestNumJunk > 0) + addJunkRecursive(&_block, bestNumJunk); + } + /// Traverses the CFG and at each block that allows junk, i.e. that is a cut-vertex that never leads to a function /// return, checks if adding junk reduces the shuffling cost upon entering and if so recursively adds junk /// to the spanned subgraph. util::BreadthFirstSearch{{&_block}}.run([&](CFG::BasicBlock const* _block, auto _addChild) { + if (_block->allowsJunk()) + { + auto& blockInfo = m_layout.blockInfos.at(_block); + Stack entryLayout = blockInfo.entryLayout; + Stack const& nextLayout = _block->operations.empty() ? blockInfo.exitLayout : m_layout.operationEntryLayout.at(&_block->operations.front()); + if (entryLayout != nextLayout) + { + size_t bestNumJunk = getBestNumJunk( + entryLayout, + nextLayout + ); + if (bestNumJunk > 0) + { + addJunkRecursive(_block, bestNumJunk); + blockInfo.entryLayout = entryLayout; + } + } + } std::visit(util::GenericVisitor{ [&](CFG::BasicBlock::MainExit const&) {}, [&](CFG::BasicBlock::Jump const& _jump) @@ -779,32 +825,6 @@ void StackLayoutGenerator::fillInJunk(CFG::BasicBlock const& _block) }, [&](CFG::BasicBlock::ConditionalJump const& _conditionalJump) { - for (CFG::BasicBlock* exit: {_conditionalJump.zero, _conditionalJump.nonZero}) - if (exit->allowsJunk()) - { - auto& blockInfo = m_layout.blockInfos.at(exit); - Stack entryLayout = blockInfo.entryLayout; - Stack nextLayout = exit->operations.empty() ? blockInfo.exitLayout : m_layout.operationEntryLayout.at(&exit->operations.front()); - - size_t bestCost = evaluateTransform(entryLayout, nextLayout); - size_t bestNumJunk = 0; - size_t maxJunk = entryLayout.size(); - for (size_t numJunk = 1; numJunk <= maxJunk; ++numJunk) - { - size_t cost = evaluateTransform(entryLayout, Stack{numJunk, JunkSlot{}} + nextLayout); - if (cost < bestCost) - { - bestCost = cost; - bestNumJunk = numJunk; - } - } - - if (bestNumJunk > 0) - { - addJunkRecursive(exit, bestNumJunk); - blockInfo.entryLayout = entryLayout; - } - } _addChild(_conditionalJump.zero); _addChild(_conditionalJump.nonZero); }, diff --git a/libyul/backends/evm/StackLayoutGenerator.h b/libyul/backends/evm/StackLayoutGenerator.h index 34cac6d39..40b554cd6 100644 --- a/libyul/backends/evm/StackLayoutGenerator.h +++ b/libyul/backends/evm/StackLayoutGenerator.h @@ -79,7 +79,7 @@ private: /// Main algorithm walking the graph from entry to exit and propagating back the stack layouts to the entries. /// Iteratively reruns itself along backwards jumps until the layout is stabilized. - void processEntryPoint(CFG::BasicBlock const& _entry); + void processEntryPoint(CFG::BasicBlock const& _entry, CFG::FunctionInfo const* _functionInfo = nullptr); /// @returns the best known exit layout of @a _block, if all dependencies are already @a _visited. /// If not, adds the dependencies to @a _dependencyList and @returns std::nullopt. @@ -112,7 +112,7 @@ private: static Stack compressStack(Stack _stack); //// Fills in junk when entering branches that do not need a clean stack in case the result is cheaper. - void fillInJunk(CFG::BasicBlock const& _block); + void fillInJunk(CFG::BasicBlock const& _block, CFG::FunctionInfo const* _functionInfo = nullptr); StackLayout& m_layout; }; diff --git a/test/libsolidity/semanticTests/abiEncoderV1/abi_encode_calldata_slice.sol b/test/libsolidity/semanticTests/abiEncoderV1/abi_encode_calldata_slice.sol index 67680b852..328de20a5 100644 --- a/test/libsolidity/semanticTests/abiEncoderV1/abi_encode_calldata_slice.sol +++ b/test/libsolidity/semanticTests/abiEncoderV1/abi_encode_calldata_slice.sol @@ -63,6 +63,6 @@ contract C { // gas legacy: 414569 // gas legacyOptimized: 319271 // test_uint256() -> -// gas irOptimized: 511919 +// gas irOptimized: 511451 // gas legacy: 581876 // gas legacyOptimized: 442757 diff --git a/test/libsolidity/semanticTests/abiEncoderV2/abi_encode_calldata_slice.sol b/test/libsolidity/semanticTests/abiEncoderV2/abi_encode_calldata_slice.sol index 0d2e4f9c0..2eef62d17 100644 --- a/test/libsolidity/semanticTests/abiEncoderV2/abi_encode_calldata_slice.sol +++ b/test/libsolidity/semanticTests/abiEncoderV2/abi_encode_calldata_slice.sol @@ -64,6 +64,6 @@ contract C { // gas legacy: 414569 // gas legacyOptimized: 319271 // test_uint256() -> -// gas irOptimized: 511919 +// gas irOptimized: 511451 // gas legacy: 581876 // gas legacyOptimized: 442757 diff --git a/test/libsolidity/semanticTests/array/array_storage_index_zeroed_test.sol b/test/libsolidity/semanticTests/array/array_storage_index_zeroed_test.sol index b1ef599e9..1d9d427ae 100644 --- a/test/libsolidity/semanticTests/array/array_storage_index_zeroed_test.sol +++ b/test/libsolidity/semanticTests/array/array_storage_index_zeroed_test.sol @@ -52,18 +52,18 @@ contract C { // ---- // test_zeroed_indicies(uint256): 1 -> // test_zeroed_indicies(uint256): 5 -> -// gas irOptimized: 132036 +// gas irOptimized: 131998 // gas legacy: 132961 // gas legacyOptimized: 130752 // test_zeroed_indicies(uint256): 10 -> -// gas irOptimized: 226094 +// gas irOptimized: 226012 // gas legacy: 228071 // gas legacyOptimized: 224010 // test_zeroed_indicies(uint256): 15 -> -// gas irOptimized: 324266 +// gas irOptimized: 324140 // gas legacy: 327311 // gas legacyOptimized: 321462 // test_zeroed_indicies(uint256): 0xFF -> -// gas irOptimized: 5122626 +// gas irOptimized: 5120200 // gas legacy: 5172987 // gas legacyOptimized: 5066462 diff --git a/test/libsolidity/semanticTests/array/pop/byte_array_pop_long_storage_empty_garbage_ref.sol b/test/libsolidity/semanticTests/array/pop/byte_array_pop_long_storage_empty_garbage_ref.sol index e801dd0fc..7ce41ffff 100644 --- a/test/libsolidity/semanticTests/array/pop/byte_array_pop_long_storage_empty_garbage_ref.sol +++ b/test/libsolidity/semanticTests/array/pop/byte_array_pop_long_storage_empty_garbage_ref.sol @@ -15,7 +15,7 @@ contract c { } // ---- // test() -> -// gas irOptimized: 114023 +// gas irOptimized: 113892 // gas legacy: 131544 // gas legacyOptimized: 126811 // storageEmpty -> 1 diff --git a/test/libsolidity/semanticTests/byte_array_to_storage_cleanup.sol b/test/libsolidity/semanticTests/byte_array_to_storage_cleanup.sol index d7a5c8bf3..e2d0ba7ac 100644 --- a/test/libsolidity/semanticTests/byte_array_to_storage_cleanup.sol +++ b/test/libsolidity/semanticTests/byte_array_to_storage_cleanup.sol @@ -28,7 +28,7 @@ contract C { // compileViaYul: also // ---- // constructor() -> -// gas irOptimized: 464753 +// gas irOptimized: 449704 // gas legacy: 729908 // gas legacyOptimized: 493347 // h() -> 0x20, 0x40, 0x00, 0 diff --git a/test/libsolidity/semanticTests/externalContracts/base64.sol b/test/libsolidity/semanticTests/externalContracts/base64.sol index 5133da878..37be5e14d 100644 --- a/test/libsolidity/semanticTests/externalContracts/base64.sol +++ b/test/libsolidity/semanticTests/externalContracts/base64.sol @@ -33,7 +33,7 @@ contract test { // EVMVersion: >=constantinople // ---- // constructor() -// gas irOptimized: 438376 +// gas irOptimized: 414909 // gas legacy: 750723 // gas legacyOptimized: 536620 // encode_inline_asm(bytes): 0x20, 0 -> 0x20, 0 @@ -51,10 +51,10 @@ contract test { // encode_no_asm(bytes): 0x20, 5, "fooba" -> 0x20, 8, "Zm9vYmE=" // encode_no_asm(bytes): 0x20, 6, "foobar" -> 0x20, 8, "Zm9vYmFy" // encode_inline_asm_large() -// gas irOptimized: 1387039 +// gas irOptimized: 1374039 // gas legacy: 1688033 // gas legacyOptimized: 1205033 // encode_no_asm_large() -// gas irOptimized: 3316107 +// gas irOptimized: 3291100 // gas legacy: 4765077 // gas legacyOptimized: 2908077 diff --git a/test/libsolidity/semanticTests/externalContracts/snark.sol b/test/libsolidity/semanticTests/externalContracts/snark.sol index 7a341898a..be9fc0072 100644 --- a/test/libsolidity/semanticTests/externalContracts/snark.sol +++ b/test/libsolidity/semanticTests/externalContracts/snark.sol @@ -294,11 +294,11 @@ contract Test { // f() -> true // g() -> true // pair() -> true -// gas irOptimized: 270080 +// gas irOptimized: 269938 // gas legacy: 275952 // gas legacyOptimized: 267239 // verifyTx() -> true // ~ emit Verified(string): 0x20, 0x16, "Successfully verified." -// gas irOptimized: 784027 +// gas irOptimized: 783501 // gas legacy: 805423 // gas legacyOptimized: 772571 diff --git a/test/libsolidity/semanticTests/externalContracts/strings.sol b/test/libsolidity/semanticTests/externalContracts/strings.sol index 654562ef0..a134fe86a 100644 --- a/test/libsolidity/semanticTests/externalContracts/strings.sol +++ b/test/libsolidity/semanticTests/externalContracts/strings.sol @@ -49,7 +49,7 @@ contract test { } // ---- // constructor() -// gas irOptimized: 670586 +// gas irOptimized: 642624 // gas legacy: 1096108 // gas legacyOptimized: 741962 // toSlice(string): 0x20, 11, "hello world" -> 11, 0xa0 @@ -69,6 +69,6 @@ contract test { // gas legacy: 31621 // gas legacyOptimized: 27914 // benchmark(string,bytes32): 0x40, 0x0842021, 8, "solidity" -> 0x2020 -// gas irOptimized: 2017770 +// gas irOptimized: 1989966 // gas legacy: 4294552 // gas legacyOptimized: 2327981 diff --git a/test/libsolidity/semanticTests/functionCall/external_call_to_nonexisting_debugstrings.sol b/test/libsolidity/semanticTests/functionCall/external_call_to_nonexisting_debugstrings.sol index 9cc2846ac..f004d9472 100644 --- a/test/libsolidity/semanticTests/functionCall/external_call_to_nonexisting_debugstrings.sol +++ b/test/libsolidity/semanticTests/functionCall/external_call_to_nonexisting_debugstrings.sol @@ -26,7 +26,7 @@ contract C { // revertStrings: debug // ---- // constructor(), 1 ether -> -// gas irOptimized: 424088 +// gas irOptimized: 391081 // gas legacy: 823681 // gas legacyOptimized: 505900 // f(uint256): 0 -> FAILURE, hex"08c379a0", 0x20, 37, "Target contract does not contain", " code" diff --git a/test/libyul/evmCodeTransform/after_terminating_function.yul b/test/libyul/evmCodeTransform/after_terminating_function.yul new file mode 100644 index 000000000..ee32b4431 --- /dev/null +++ b/test/libyul/evmCodeTransform/after_terminating_function.yul @@ -0,0 +1,22 @@ +{ + let b := f(1, 2) + function f(a, r) -> t { revert(0, 0) } + b := f(3, 4) +} +// ==== +// stackOptimization: true +// ---- +// /* "":17:18 */ +// 0x02 +// /* "":14:15 */ +// 0x01 +// /* "":12:19 */ +// tag_1 +// jump // in +// /* "":21:59 */ +// tag_1: +// /* "":55:56 */ +// 0x00 +// /* "":45:57 */ +// dup1 +// revert diff --git a/test/libyul/yulControlFlowGraph/function.yul b/test/libyul/yulControlFlowGraph/function.yul index 862b1b9e6..90c4b380e 100644 --- a/test/libyul/yulControlFlowGraph/function.yul +++ b/test/libyul/yulControlFlowGraph/function.yul @@ -28,10 +28,9 @@ // Block0 [label="\ // i: [ RET[i] ] => [ TMP[i, 0] TMP[i, 1] ]\l\ // Assignment(x, y): [ TMP[i, 0] TMP[i, 1] ] => [ x y ]\l\ -// h: [ RET[h] x ] => [ ]\l\ -// h: [ RET[h] y ] => [ ]\l\ +// h: [ x ] => [ ]\l\ // "]; -// Block0Exit [label="MainExit"]; +// Block0Exit [label="Terminated"]; // Block0 -> Block0Exit; // // FunctionEntry_f_1 [label="function f(a, b) -> r"]; @@ -57,10 +56,9 @@ // FunctionEntry_h_3 -> Block3; // Block3 [label="\ // f: [ RET[f] 0x00 x ] => [ TMP[f, 0] ]\l\ -// h: [ RET[h] TMP[f, 0] ] => [ ]\l\ -// g: [ RET[g] ] => [ ]\l\ +// h: [ TMP[f, 0] ] => [ ]\l\ // "]; -// Block3Exit [label="FunctionReturn[h]"]; +// Block3Exit [label="Terminated"]; // Block3 -> Block3Exit; // // FunctionEntry_i_4 [label="function i() -> v, w"]; diff --git a/test/libyul/yulStackLayout/function.yul b/test/libyul/yulStackLayout/function.yul index 343555123..ccf97422f 100644 --- a/test/libyul/yulStackLayout/function.yul +++ b/test/libyul/yulStackLayout/function.yul @@ -27,21 +27,18 @@ // Entry -> Block0; // Block0 [label="\ // [ ]\l\ -// [ RET[h] RET[h] RET[i] ]\l\ +// [ RET[i] ]\l\ // i\l\ -// [ RET[h] RET[h] TMP[i, 0] TMP[i, 1] ]\l\ -// [ RET[h] RET[h] TMP[i, 0] TMP[i, 1] ]\l\ +// [ TMP[i, 0] TMP[i, 1] ]\l\ +// [ TMP[i, 0] TMP[i, 1] ]\l\ // Assignment(x, y)\l\ -// [ RET[h] RET[h] x y ]\l\ -// [ RET[h] y RET[h] x ]\l\ -// h\l\ -// [ RET[h] y ]\l\ -// [ RET[h] y ]\l\ +// [ x y ]\l\ +// [ x ]\l\ // h\l\ // [ ]\l\ // [ ]\l\ // "]; -// Block0Exit [label="MainExit"]; +// Block0Exit [label="Terminated"]; // Block0 -> Block0Exit; // // FunctionEntry_f [label="function f(a, b) -> r\l\ @@ -83,19 +80,16 @@ // [ RET x ]"]; // FunctionEntry_h -> Block3; // Block3 [label="\ -// [ RET RET[h] RET[f] 0x00 x ]\l\ -// [ RET RET[h] RET[f] 0x00 x ]\l\ +// [ RET[f] 0x00 x ]\l\ +// [ RET[f] 0x00 x ]\l\ // f\l\ -// [ RET RET[h] TMP[f, 0] ]\l\ -// [ RET RET[h] TMP[f, 0] ]\l\ +// [ TMP[f, 0] ]\l\ +// [ TMP[f, 0] ]\l\ // h\l\ -// [ RET ]\l\ -// [ RET RET[g] ]\l\ -// g\l\ -// [ RET ]\l\ -// [ RET ]\l\ +// [ ]\l\ +// [ ]\l\ // "]; -// Block3Exit [label="FunctionReturn[h]"]; +// Block3Exit [label="Terminated"]; // Block3 -> Block3Exit; // // FunctionEntry_i [label="function i() -> v, w\l\