mirror of
				https://github.com/ethereum/solidity
				synced 2023-10-03 13:03:40 +00:00 
			
		
		
		
	Use side effects of user-defined functions in other optimizer steps.
This commit is contained in:
		
							parent
							
								
									5eb97fa6ee
								
							
						
					
					
						commit
						d6c461ed61
					
				| @ -19,6 +19,7 @@ | ||||
| #include <libyul/optimiser/Semantics.h> | ||||
| #include <libyul/AST.h> | ||||
| #include <libyul/optimiser/NameCollector.h> | ||||
| #include <libyul/ControlFlowSideEffectsCollector.h> | ||||
| #include <libsolutil/CommonData.h> | ||||
| 
 | ||||
| using namespace std; | ||||
| @ -26,6 +27,12 @@ using namespace solidity; | ||||
| using namespace solidity::yul; | ||||
| using namespace solidity::util; | ||||
| 
 | ||||
| void ConditionalSimplifier::run(OptimiserStepContext& _context, Block& _ast) | ||||
| { | ||||
| 	ControlFlowSideEffectsCollector sideEffects(_context.dialect, _ast); | ||||
| 	ConditionalSimplifier{_context.dialect, sideEffects.functionSideEffects()}(_ast); | ||||
| } | ||||
| 
 | ||||
| void ConditionalSimplifier::operator()(Switch& _switch) | ||||
| { | ||||
| 	visit(*_switch.expression); | ||||
| @ -65,7 +72,7 @@ void ConditionalSimplifier::operator()(Block& _block) | ||||
| 				if ( | ||||
| 					holds_alternative<Identifier>(*_if.condition) && | ||||
| 					!_if.body.statements.empty() && | ||||
| 					TerminationFinder(m_dialect).controlFlowKind(_if.body.statements.back()) != | ||||
| 					TerminationFinder(m_dialect, &m_functionSideEffects).controlFlowKind(_if.body.statements.back()) != | ||||
| 						TerminationFinder::ControlFlow::FlowOut | ||||
| 				) | ||||
| 				{ | ||||
|  | ||||
| @ -44,7 +44,6 @@ namespace solidity::yul | ||||
|  * | ||||
|  * Future features: | ||||
|  *  - allow replacements by "1" | ||||
|  *  - take termination of user-defined functions into account | ||||
|  * | ||||
|  * Works best with SSA form and if dead code removal has run before. | ||||
|  * | ||||
| @ -54,20 +53,21 @@ class ConditionalSimplifier: public ASTModifier | ||||
| { | ||||
| public: | ||||
| 	static constexpr char const* name{"ConditionalSimplifier"}; | ||||
| 	static void run(OptimiserStepContext& _context, Block& _ast) | ||||
| 	{ | ||||
| 		ConditionalSimplifier{_context.dialect}(_ast); | ||||
| 	} | ||||
| 	static void run(OptimiserStepContext& _context, Block& _ast); | ||||
| 
 | ||||
| 	using ASTModifier::operator(); | ||||
| 	void operator()(Switch& _switch) override; | ||||
| 	void operator()(Block& _block) override; | ||||
| 
 | ||||
| private: | ||||
| 	explicit ConditionalSimplifier(Dialect const& _dialect): | ||||
| 		m_dialect(_dialect) | ||||
| 	explicit ConditionalSimplifier( | ||||
| 		Dialect const& _dialect, | ||||
| 		std::map<YulString, ControlFlowSideEffects> const& _sideEffects | ||||
| 	): | ||||
| 		m_dialect(_dialect), m_functionSideEffects(_sideEffects) | ||||
| 	{} | ||||
| 	Dialect const& m_dialect; | ||||
| 	std::map<YulString, ControlFlowSideEffects> const& m_functionSideEffects; | ||||
| }; | ||||
| 
 | ||||
| } | ||||
|  | ||||
| @ -20,6 +20,7 @@ | ||||
| #include <libyul/AST.h> | ||||
| #include <libyul/Utilities.h> | ||||
| #include <libyul/optimiser/NameCollector.h> | ||||
| #include <libyul/ControlFlowSideEffectsCollector.h> | ||||
| #include <libsolutil/CommonData.h> | ||||
| 
 | ||||
| using namespace std; | ||||
| @ -27,6 +28,12 @@ using namespace solidity; | ||||
| using namespace solidity::yul; | ||||
| using namespace solidity::util; | ||||
| 
 | ||||
| void ConditionalUnsimplifier::run(OptimiserStepContext& _context, Block& _ast) | ||||
| { | ||||
| 	ControlFlowSideEffectsCollector sideEffects(_context.dialect, _ast); | ||||
| 	ConditionalUnsimplifier{_context.dialect, sideEffects.functionSideEffects()}(_ast); | ||||
| } | ||||
| 
 | ||||
| void ConditionalUnsimplifier::operator()(Switch& _switch) | ||||
| { | ||||
| 	visit(*_switch.expression); | ||||
| @ -78,7 +85,7 @@ void ConditionalUnsimplifier::operator()(Block& _block) | ||||
| 					YulString condition = std::get<Identifier>(*_if.condition).name; | ||||
| 					if ( | ||||
| 						holds_alternative<Assignment>(_stmt2) && | ||||
| 						TerminationFinder(m_dialect).controlFlowKind(_if.body.statements.back()) != | ||||
| 						TerminationFinder(m_dialect, &m_functionSideEffects).controlFlowKind(_if.body.statements.back()) != | ||||
| 							TerminationFinder::ControlFlow::FlowOut | ||||
| 					) | ||||
| 					{ | ||||
|  | ||||
| @ -33,20 +33,21 @@ class ConditionalUnsimplifier: public ASTModifier | ||||
| { | ||||
| public: | ||||
| 	static constexpr char const* name{"ConditionalUnsimplifier"}; | ||||
| 	static void run(OptimiserStepContext& _context, Block& _ast) | ||||
| 	{ | ||||
| 		ConditionalUnsimplifier{_context.dialect}(_ast); | ||||
| 	} | ||||
| 	static void run(OptimiserStepContext& _context, Block& _ast); | ||||
| 
 | ||||
| 	using ASTModifier::operator(); | ||||
| 	void operator()(Switch& _switch) override; | ||||
| 	void operator()(Block& _block) override; | ||||
| 
 | ||||
| private: | ||||
| 	explicit ConditionalUnsimplifier(Dialect const& _dialect): | ||||
| 		m_dialect(_dialect) | ||||
| 	explicit ConditionalUnsimplifier( | ||||
| 		Dialect const& _dialect, | ||||
| 		std::map<YulString, ControlFlowSideEffects> const& _sideEffects | ||||
| 	): | ||||
| 		m_dialect(_dialect), m_functionSideEffects(_sideEffects) | ||||
| 	{} | ||||
| 	Dialect const& m_dialect; | ||||
| 	std::map<YulString, ControlFlowSideEffects> const& m_functionSideEffects; | ||||
| }; | ||||
| 
 | ||||
| } | ||||
|  | ||||
| @ -22,6 +22,7 @@ | ||||
| #include <libyul/optimiser/DeadCodeEliminator.h> | ||||
| #include <libyul/optimiser/Semantics.h> | ||||
| #include <libyul/optimiser/OptimiserStep.h> | ||||
| #include <libyul/ControlFlowSideEffectsCollector.h> | ||||
| #include <libyul/AST.h> | ||||
| 
 | ||||
| #include <libevmasm/SemanticInformation.h> | ||||
| @ -36,7 +37,11 @@ using namespace solidity::yul; | ||||
| 
 | ||||
| void DeadCodeEliminator::run(OptimiserStepContext& _context, Block& _ast) | ||||
| { | ||||
| 	DeadCodeEliminator{_context.dialect}(_ast); | ||||
| 	ControlFlowSideEffectsCollector sideEffects(_context.dialect, _ast); | ||||
| 	DeadCodeEliminator{ | ||||
| 		_context.dialect, | ||||
| 		sideEffects.functionSideEffects() | ||||
| 	}(_ast); | ||||
| } | ||||
| 
 | ||||
| void DeadCodeEliminator::operator()(ForLoop& _for) | ||||
| @ -49,7 +54,7 @@ void DeadCodeEliminator::operator()(Block& _block) | ||||
| { | ||||
| 	TerminationFinder::ControlFlow controlFlowChange; | ||||
| 	size_t index; | ||||
| 	tie(controlFlowChange, index) = TerminationFinder{m_dialect}.firstUnconditionalControlFlowChange(_block.statements); | ||||
| 	tie(controlFlowChange, index) = TerminationFinder{m_dialect, &m_functionSideEffects}.firstUnconditionalControlFlowChange(_block.statements); | ||||
| 
 | ||||
| 	// Erase everything after the terminating statement that is not a function definition.
 | ||||
| 	if (controlFlowChange != TerminationFinder::ControlFlow::FlowOut && index != std::numeric_limits<size_t>::max()) | ||||
|  | ||||
| @ -31,12 +31,15 @@ namespace solidity::yul | ||||
| { | ||||
| struct Dialect; | ||||
| struct OptimiserStepContext; | ||||
| struct ControlFlowSideEffects; | ||||
| 
 | ||||
| /**
 | ||||
|  * Optimisation stage that removes unreachable code | ||||
|  * | ||||
|  * Unreachable code is any code within a block which is preceded by a | ||||
|  * leave, return, invalid, break, continue, selfdestruct or revert. | ||||
|  * leave, return, invalid, break, continue, selfdestruct or revert or | ||||
|  * a call to a user-defined function that never returns (either due to | ||||
|  * recursion or a call to return / revert / stop). | ||||
|  * | ||||
|  * Function definitions are retained as they might be called by earlier | ||||
|  * code and thus are considered reachable. | ||||
| @ -57,9 +60,13 @@ public: | ||||
| 	void operator()(Block& _block) override; | ||||
| 
 | ||||
| private: | ||||
| 	DeadCodeEliminator(Dialect const& _dialect): m_dialect(_dialect) {} | ||||
| 	DeadCodeEliminator( | ||||
| 		Dialect const& _dialect, | ||||
| 		std::map<YulString, ControlFlowSideEffects> const& _sideEffects | ||||
| 	): m_dialect(_dialect), m_functionSideEffects(_sideEffects) {} | ||||
| 
 | ||||
| 	Dialect const& m_dialect; | ||||
| 	std::map<YulString, ControlFlowSideEffects> const& m_functionSideEffects; | ||||
| }; | ||||
| 
 | ||||
| } | ||||
|  | ||||
| @ -182,8 +182,19 @@ pair<TerminationFinder::ControlFlow, size_t> TerminationFinder::firstUncondition | ||||
| TerminationFinder::ControlFlow TerminationFinder::controlFlowKind(Statement const& _statement) | ||||
| { | ||||
| 	if ( | ||||
| 		holds_alternative<VariableDeclaration>(_statement) && | ||||
| 		std::get<VariableDeclaration>(_statement).value && | ||||
| 		containsNonContinuingFunctionCall(*std::get<VariableDeclaration>(_statement).value) | ||||
| 	) | ||||
| 		return ControlFlow::Terminate; | ||||
| 	else if ( | ||||
| 		holds_alternative<Assignment>(_statement) && | ||||
| 		containsNonContinuingFunctionCall(*std::get<Assignment>(_statement).value) | ||||
| 	) | ||||
| 		return ControlFlow::Terminate; | ||||
| 	else if ( | ||||
| 		holds_alternative<ExpressionStatement>(_statement) && | ||||
| 		isTerminatingBuiltin(std::get<ExpressionStatement>(_statement)) | ||||
| 		containsNonContinuingFunctionCall(std::get<ExpressionStatement>(_statement).expression) | ||||
| 	) | ||||
| 		return ControlFlow::Terminate; | ||||
| 	else if (holds_alternative<Break>(_statement)) | ||||
| @ -196,10 +207,18 @@ TerminationFinder::ControlFlow TerminationFinder::controlFlowKind(Statement cons | ||||
| 		return ControlFlow::FlowOut; | ||||
| } | ||||
| 
 | ||||
| bool TerminationFinder::isTerminatingBuiltin(ExpressionStatement const& _exprStmnt) | ||||
| bool TerminationFinder::containsNonContinuingFunctionCall(Expression const& _expr) | ||||
| { | ||||
| 	if (holds_alternative<FunctionCall>(_exprStmnt.expression)) | ||||
| 		if (auto instruction = toEVMInstruction(m_dialect, std::get<FunctionCall>(_exprStmnt.expression).functionName.name)) | ||||
| 			return evmasm::SemanticInformation::terminatesControlFlow(*instruction); | ||||
| 	if (auto functionCall = std::get_if<FunctionCall>(&_expr)) | ||||
| 	{ | ||||
| 		for (auto const& arg: functionCall->arguments) | ||||
| 			if (containsNonContinuingFunctionCall(arg)) | ||||
| 				return true; | ||||
| 
 | ||||
| 		if (auto builtin = m_dialect.builtin(functionCall->functionName.name)) | ||||
| 			return !builtin->controlFlowSideEffects.canContinue; | ||||
| 		else if (m_functionSideEffects && m_functionSideEffects->count(functionCall->functionName.name)) | ||||
| 			return !m_functionSideEffects->at(functionCall->functionName.name).canContinue; | ||||
| 	} | ||||
| 	return false; | ||||
| } | ||||
|  | ||||
| @ -205,22 +205,31 @@ private: | ||||
| 	std::set<YulString> m_variableReferences; | ||||
| }; | ||||
| 
 | ||||
| struct ControlFlowSideEffects; | ||||
| 
 | ||||
| /**
 | ||||
|  * Helper class to find "irregular" control flow. | ||||
|  * This includes termination, break and continue. | ||||
|  * This includes termination, break, continue and leave. | ||||
|  * In general, it is applied only to "simple" statements. The control-flow | ||||
|  * of loops, switches and if statements is always "FlowOut" with the assumption | ||||
|  * that the caller will descend into them. | ||||
|  */ | ||||
| class TerminationFinder | ||||
| { | ||||
| public: | ||||
| 	// TODO check all uses of TerminationFinder!
 | ||||
| 	/// "Terminate" here means that there is no continuing control-flow.
 | ||||
| 	/// If this is applied to a function that can revert or stop, but can also
 | ||||
| 	/// exit regularly, the property is set to "FlowOut".
 | ||||
| 	enum class ControlFlow { FlowOut, Break, Continue, Terminate, Leave }; | ||||
| 
 | ||||
| 	TerminationFinder(Dialect const& _dialect): m_dialect(_dialect) {} | ||||
| 	TerminationFinder( | ||||
| 		Dialect const& _dialect, | ||||
| 		std::map<YulString, ControlFlowSideEffects> const* _functionSideEffects = nullptr | ||||
| 	): m_dialect(_dialect), m_functionSideEffects(_functionSideEffects) {} | ||||
| 
 | ||||
| 	/// @returns the index of the first statement in the provided sequence
 | ||||
| 	/// that is an unconditional ``break``, ``continue``, ``leave`` or a
 | ||||
| 	/// call to a terminating builtin function.
 | ||||
| 	/// call to a terminating function.
 | ||||
| 	/// If control flow can continue at the end of the list,
 | ||||
| 	/// returns `FlowOut` and ``size_t(-1)``.
 | ||||
| 	/// The function might return ``FlowOut`` even though control
 | ||||
| @ -233,13 +242,14 @@ public: | ||||
| 	/// This function could return FlowOut even if control flow never continues.
 | ||||
| 	ControlFlow controlFlowKind(Statement const& _statement); | ||||
| 
 | ||||
| 	/// @returns true if the expression statement is a direct
 | ||||
| 	/// call to a builtin terminating function like
 | ||||
| 	/// ``stop``, ``revert`` or ``return``.
 | ||||
| 	bool isTerminatingBuiltin(ExpressionStatement const& _exprStmnt); | ||||
| 	/// @returns true if the expression contains a
 | ||||
| 	/// call to a terminating function, i.e. a function that does not have
 | ||||
| 	/// a regular "flow out" control-flow (it might also be recursive).
 | ||||
| 	bool containsNonContinuingFunctionCall(Expression const& _expr); | ||||
| 
 | ||||
| private: | ||||
| 	Dialect const& m_dialect; | ||||
| 	std::map<YulString, ControlFlowSideEffects> const* m_functionSideEffects; | ||||
| }; | ||||
| 
 | ||||
| } | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user