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/optimiser/Semantics.h> | ||||||
| #include <libyul/AST.h> | #include <libyul/AST.h> | ||||||
| #include <libyul/optimiser/NameCollector.h> | #include <libyul/optimiser/NameCollector.h> | ||||||
|  | #include <libyul/ControlFlowSideEffectsCollector.h> | ||||||
| #include <libsolutil/CommonData.h> | #include <libsolutil/CommonData.h> | ||||||
| 
 | 
 | ||||||
| using namespace std; | using namespace std; | ||||||
| @ -26,6 +27,12 @@ using namespace solidity; | |||||||
| using namespace solidity::yul; | using namespace solidity::yul; | ||||||
| using namespace solidity::util; | 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) | void ConditionalSimplifier::operator()(Switch& _switch) | ||||||
| { | { | ||||||
| 	visit(*_switch.expression); | 	visit(*_switch.expression); | ||||||
| @ -65,7 +72,7 @@ void ConditionalSimplifier::operator()(Block& _block) | |||||||
| 				if ( | 				if ( | ||||||
| 					holds_alternative<Identifier>(*_if.condition) && | 					holds_alternative<Identifier>(*_if.condition) && | ||||||
| 					!_if.body.statements.empty() && | 					!_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 | 						TerminationFinder::ControlFlow::FlowOut | ||||||
| 				) | 				) | ||||||
| 				{ | 				{ | ||||||
|  | |||||||
| @ -44,7 +44,6 @@ namespace solidity::yul | |||||||
|  * |  * | ||||||
|  * Future features: |  * Future features: | ||||||
|  *  - allow replacements by "1" |  *  - 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. |  * Works best with SSA form and if dead code removal has run before. | ||||||
|  * |  * | ||||||
| @ -54,20 +53,21 @@ class ConditionalSimplifier: public ASTModifier | |||||||
| { | { | ||||||
| public: | public: | ||||||
| 	static constexpr char const* name{"ConditionalSimplifier"}; | 	static constexpr char const* name{"ConditionalSimplifier"}; | ||||||
| 	static void run(OptimiserStepContext& _context, Block& _ast) | 	static void run(OptimiserStepContext& _context, Block& _ast); | ||||||
| 	{ |  | ||||||
| 		ConditionalSimplifier{_context.dialect}(_ast); |  | ||||||
| 	} |  | ||||||
| 
 | 
 | ||||||
| 	using ASTModifier::operator(); | 	using ASTModifier::operator(); | ||||||
| 	void operator()(Switch& _switch) override; | 	void operator()(Switch& _switch) override; | ||||||
| 	void operator()(Block& _block) override; | 	void operator()(Block& _block) override; | ||||||
| 
 | 
 | ||||||
| private: | private: | ||||||
| 	explicit ConditionalSimplifier(Dialect const& _dialect): | 	explicit ConditionalSimplifier( | ||||||
| 		m_dialect(_dialect) | 		Dialect const& _dialect, | ||||||
|  | 		std::map<YulString, ControlFlowSideEffects> const& _sideEffects | ||||||
|  | 	): | ||||||
|  | 		m_dialect(_dialect), m_functionSideEffects(_sideEffects) | ||||||
| 	{} | 	{} | ||||||
| 	Dialect const& m_dialect; | 	Dialect const& m_dialect; | ||||||
|  | 	std::map<YulString, ControlFlowSideEffects> const& m_functionSideEffects; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| } | } | ||||||
|  | |||||||
| @ -20,6 +20,7 @@ | |||||||
| #include <libyul/AST.h> | #include <libyul/AST.h> | ||||||
| #include <libyul/Utilities.h> | #include <libyul/Utilities.h> | ||||||
| #include <libyul/optimiser/NameCollector.h> | #include <libyul/optimiser/NameCollector.h> | ||||||
|  | #include <libyul/ControlFlowSideEffectsCollector.h> | ||||||
| #include <libsolutil/CommonData.h> | #include <libsolutil/CommonData.h> | ||||||
| 
 | 
 | ||||||
| using namespace std; | using namespace std; | ||||||
| @ -27,6 +28,12 @@ using namespace solidity; | |||||||
| using namespace solidity::yul; | using namespace solidity::yul; | ||||||
| using namespace solidity::util; | 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) | void ConditionalUnsimplifier::operator()(Switch& _switch) | ||||||
| { | { | ||||||
| 	visit(*_switch.expression); | 	visit(*_switch.expression); | ||||||
| @ -78,7 +85,7 @@ void ConditionalUnsimplifier::operator()(Block& _block) | |||||||
| 					YulString condition = std::get<Identifier>(*_if.condition).name; | 					YulString condition = std::get<Identifier>(*_if.condition).name; | ||||||
| 					if ( | 					if ( | ||||||
| 						holds_alternative<Assignment>(_stmt2) && | 						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 | 							TerminationFinder::ControlFlow::FlowOut | ||||||
| 					) | 					) | ||||||
| 					{ | 					{ | ||||||
|  | |||||||
| @ -33,20 +33,21 @@ class ConditionalUnsimplifier: public ASTModifier | |||||||
| { | { | ||||||
| public: | public: | ||||||
| 	static constexpr char const* name{"ConditionalUnsimplifier"}; | 	static constexpr char const* name{"ConditionalUnsimplifier"}; | ||||||
| 	static void run(OptimiserStepContext& _context, Block& _ast) | 	static void run(OptimiserStepContext& _context, Block& _ast); | ||||||
| 	{ |  | ||||||
| 		ConditionalUnsimplifier{_context.dialect}(_ast); |  | ||||||
| 	} |  | ||||||
| 
 | 
 | ||||||
| 	using ASTModifier::operator(); | 	using ASTModifier::operator(); | ||||||
| 	void operator()(Switch& _switch) override; | 	void operator()(Switch& _switch) override; | ||||||
| 	void operator()(Block& _block) override; | 	void operator()(Block& _block) override; | ||||||
| 
 | 
 | ||||||
| private: | private: | ||||||
| 	explicit ConditionalUnsimplifier(Dialect const& _dialect): | 	explicit ConditionalUnsimplifier( | ||||||
| 		m_dialect(_dialect) | 		Dialect const& _dialect, | ||||||
|  | 		std::map<YulString, ControlFlowSideEffects> const& _sideEffects | ||||||
|  | 	): | ||||||
|  | 		m_dialect(_dialect), m_functionSideEffects(_sideEffects) | ||||||
| 	{} | 	{} | ||||||
| 	Dialect const& m_dialect; | 	Dialect const& m_dialect; | ||||||
|  | 	std::map<YulString, ControlFlowSideEffects> const& m_functionSideEffects; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| } | } | ||||||
|  | |||||||
| @ -22,6 +22,7 @@ | |||||||
| #include <libyul/optimiser/DeadCodeEliminator.h> | #include <libyul/optimiser/DeadCodeEliminator.h> | ||||||
| #include <libyul/optimiser/Semantics.h> | #include <libyul/optimiser/Semantics.h> | ||||||
| #include <libyul/optimiser/OptimiserStep.h> | #include <libyul/optimiser/OptimiserStep.h> | ||||||
|  | #include <libyul/ControlFlowSideEffectsCollector.h> | ||||||
| #include <libyul/AST.h> | #include <libyul/AST.h> | ||||||
| 
 | 
 | ||||||
| #include <libevmasm/SemanticInformation.h> | #include <libevmasm/SemanticInformation.h> | ||||||
| @ -36,7 +37,11 @@ using namespace solidity::yul; | |||||||
| 
 | 
 | ||||||
| void DeadCodeEliminator::run(OptimiserStepContext& _context, Block& _ast) | 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) | void DeadCodeEliminator::operator()(ForLoop& _for) | ||||||
| @ -49,7 +54,7 @@ void DeadCodeEliminator::operator()(Block& _block) | |||||||
| { | { | ||||||
| 	TerminationFinder::ControlFlow controlFlowChange; | 	TerminationFinder::ControlFlow controlFlowChange; | ||||||
| 	size_t index; | 	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.
 | 	// Erase everything after the terminating statement that is not a function definition.
 | ||||||
| 	if (controlFlowChange != TerminationFinder::ControlFlow::FlowOut && index != std::numeric_limits<size_t>::max()) | 	if (controlFlowChange != TerminationFinder::ControlFlow::FlowOut && index != std::numeric_limits<size_t>::max()) | ||||||
|  | |||||||
| @ -31,12 +31,15 @@ namespace solidity::yul | |||||||
| { | { | ||||||
| struct Dialect; | struct Dialect; | ||||||
| struct OptimiserStepContext; | struct OptimiserStepContext; | ||||||
|  | struct ControlFlowSideEffects; | ||||||
| 
 | 
 | ||||||
| /**
 | /**
 | ||||||
|  * Optimisation stage that removes unreachable code |  * Optimisation stage that removes unreachable code | ||||||
|  * |  * | ||||||
|  * Unreachable code is any code within a block which is preceded by a |  * 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 |  * Function definitions are retained as they might be called by earlier | ||||||
|  * code and thus are considered reachable. |  * code and thus are considered reachable. | ||||||
| @ -57,9 +60,13 @@ public: | |||||||
| 	void operator()(Block& _block) override; | 	void operator()(Block& _block) override; | ||||||
| 
 | 
 | ||||||
| private: | 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; | 	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) | TerminationFinder::ControlFlow TerminationFinder::controlFlowKind(Statement const& _statement) | ||||||
| { | { | ||||||
| 	if ( | 	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) && | 		holds_alternative<ExpressionStatement>(_statement) && | ||||||
| 		isTerminatingBuiltin(std::get<ExpressionStatement>(_statement)) | 		containsNonContinuingFunctionCall(std::get<ExpressionStatement>(_statement).expression) | ||||||
| 	) | 	) | ||||||
| 		return ControlFlow::Terminate; | 		return ControlFlow::Terminate; | ||||||
| 	else if (holds_alternative<Break>(_statement)) | 	else if (holds_alternative<Break>(_statement)) | ||||||
| @ -196,10 +207,18 @@ TerminationFinder::ControlFlow TerminationFinder::controlFlowKind(Statement cons | |||||||
| 		return ControlFlow::FlowOut; | 		return ControlFlow::FlowOut; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool TerminationFinder::isTerminatingBuiltin(ExpressionStatement const& _exprStmnt) | bool TerminationFinder::containsNonContinuingFunctionCall(Expression const& _expr) | ||||||
| { | { | ||||||
| 	if (holds_alternative<FunctionCall>(_exprStmnt.expression)) | 	if (auto functionCall = std::get_if<FunctionCall>(&_expr)) | ||||||
| 		if (auto instruction = toEVMInstruction(m_dialect, std::get<FunctionCall>(_exprStmnt.expression).functionName.name)) | 	{ | ||||||
| 			return evmasm::SemanticInformation::terminatesControlFlow(*instruction); | 		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; | 	return false; | ||||||
| } | } | ||||||
|  | |||||||
| @ -205,22 +205,31 @@ private: | |||||||
| 	std::set<YulString> m_variableReferences; | 	std::set<YulString> m_variableReferences; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | struct ControlFlowSideEffects; | ||||||
| 
 | 
 | ||||||
| /**
 | /**
 | ||||||
|  * Helper class to find "irregular" control flow. |  * 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 | class TerminationFinder | ||||||
| { | { | ||||||
| public: | 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 }; | 	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
 | 	/// @returns the index of the first statement in the provided sequence
 | ||||||
| 	/// that is an unconditional ``break``, ``continue``, ``leave`` or a
 | 	/// 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,
 | 	/// If control flow can continue at the end of the list,
 | ||||||
| 	/// returns `FlowOut` and ``size_t(-1)``.
 | 	/// returns `FlowOut` and ``size_t(-1)``.
 | ||||||
| 	/// The function might return ``FlowOut`` even though control
 | 	/// The function might return ``FlowOut`` even though control
 | ||||||
| @ -233,13 +242,14 @@ public: | |||||||
| 	/// This function could return FlowOut even if control flow never continues.
 | 	/// This function could return FlowOut even if control flow never continues.
 | ||||||
| 	ControlFlow controlFlowKind(Statement const& _statement); | 	ControlFlow controlFlowKind(Statement const& _statement); | ||||||
| 
 | 
 | ||||||
| 	/// @returns true if the expression statement is a direct
 | 	/// @returns true if the expression contains a
 | ||||||
| 	/// call to a builtin terminating function like
 | 	/// call to a terminating function, i.e. a function that does not have
 | ||||||
| 	/// ``stop``, ``revert`` or ``return``.
 | 	/// a regular "flow out" control-flow (it might also be recursive).
 | ||||||
| 	bool isTerminatingBuiltin(ExpressionStatement const& _exprStmnt); | 	bool containsNonContinuingFunctionCall(Expression const& _expr); | ||||||
| 
 | 
 | ||||||
| private: | private: | ||||||
| 	Dialect const& m_dialect; | 	Dialect const& m_dialect; | ||||||
|  | 	std::map<YulString, ControlFlowSideEffects> const* m_functionSideEffects; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| } | } | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user