mirror of
				https://github.com/ethereum/solidity
				synced 2023-10-03 13:03:40 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			547 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			547 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /*
 | |
| 	This file is part of solidity.
 | |
| 
 | |
| 	solidity is free software: you can redistribute it and/or modify
 | |
| 	it under the terms of the GNU General Public License as published by
 | |
| 	the Free Software Foundation, either version 3 of the License, or
 | |
| 	(at your option) any later version.
 | |
| 
 | |
| 	solidity is distributed in the hope that it will be useful,
 | |
| 	but WITHOUT ANY WARRANTY; without even the implied warranty of
 | |
| 	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | |
| 	GNU General Public License for more details.
 | |
| 
 | |
| 	You should have received a copy of the GNU General Public License
 | |
| 	along with solidity.  If not, see <http://www.gnu.org/licenses/>.
 | |
| */
 | |
| // SPDX-License-Identifier: GPL-3.0
 | |
| /**
 | |
|  * Transformation of a Yul AST into a control flow graph.
 | |
|  */
 | |
| 
 | |
| #include <libyul/backends/evm/ControlFlowGraphBuilder.h>
 | |
| #include <libyul/AST.h>
 | |
| #include <libyul/Exceptions.h>
 | |
| #include <libyul/Utilities.h>
 | |
| 
 | |
| #include <libsolutil/cxx20.h>
 | |
| #include <libsolutil/Visitor.h>
 | |
| #include <libsolutil/Algorithms.h>
 | |
| 
 | |
| #include <range/v3/action/push_back.hpp>
 | |
| #include <range/v3/action/erase.hpp>
 | |
| #include <range/v3/range/conversion.hpp>
 | |
| #include <range/v3/view/concat.hpp>
 | |
| #include <range/v3/view/drop_last.hpp>
 | |
| #include <range/v3/view/enumerate.hpp>
 | |
| #include <range/v3/view/filter.hpp>
 | |
| #include <range/v3/view/iota.hpp>
 | |
| #include <range/v3/view/map.hpp>
 | |
| #include <range/v3/view/reverse.hpp>
 | |
| #include <range/v3/view/single.hpp>
 | |
| #include <range/v3/view/take_last.hpp>
 | |
| #include <range/v3/view/transform.hpp>
 | |
| 
 | |
| using namespace solidity;
 | |
| using namespace solidity::yul;
 | |
| using namespace std;
 | |
| 
 | |
| namespace
 | |
| {
 | |
| // Removes edges to blocks that are not reachable.
 | |
| void cleanUnreachable(CFG& _cfg)
 | |
| {
 | |
| 	// Determine which blocks are reachable from the entry.
 | |
| 	util::BreadthFirstSearch<CFG::BasicBlock*> reachabilityCheck{{_cfg.entry}};
 | |
| 	for (auto const& functionInfo: _cfg.functionInfo | ranges::views::values)
 | |
| 		reachabilityCheck.verticesToTraverse.emplace_back(functionInfo.entry);
 | |
| 
 | |
| 	reachabilityCheck.run([&](CFG::BasicBlock* _node, auto&& _addChild) {
 | |
| 		visit(util::GenericVisitor{
 | |
| 			[&](CFG::BasicBlock::Jump const& _jump) {
 | |
| 				_addChild(_jump.target);
 | |
| 			},
 | |
| 			[&](CFG::BasicBlock::ConditionalJump const& _jump) {
 | |
| 				_addChild(_jump.zero);
 | |
| 				_addChild(_jump.nonZero);
 | |
| 			},
 | |
| 			[](CFG::BasicBlock::FunctionReturn const&) {},
 | |
| 			[](CFG::BasicBlock::Terminated const&) {},
 | |
| 			[](CFG::BasicBlock::MainExit const&) {}
 | |
| 		}, _node->exit);
 | |
| 	});
 | |
| 
 | |
| 	// Remove all entries from unreachable nodes from the graph.
 | |
| 	for (CFG::BasicBlock* node: reachabilityCheck.visited)
 | |
| 		cxx20::erase_if(node->entries, [&](CFG::BasicBlock* entry) -> bool {
 | |
| 			return !reachabilityCheck.visited.count(entry);
 | |
| 		});
 | |
| }
 | |
| // Sets the ``recursive`` member to ``true`` for all recursive function calls.
 | |
| void markRecursiveCalls(CFG& _cfg)
 | |
| {
 | |
| 	map<CFG::BasicBlock*, vector<CFG::FunctionCall*>> callsPerBlock;
 | |
| 	auto const& findCalls = [&](CFG::BasicBlock* _block)
 | |
| 	{
 | |
| 		if (auto* calls = util::valueOrNullptr(callsPerBlock, _block))
 | |
| 			return *calls;
 | |
| 		vector<CFG::FunctionCall*>& calls = callsPerBlock[_block];
 | |
| 		util::BreadthFirstSearch<CFG::BasicBlock*>{{_block}}.run([&](CFG::BasicBlock* _block, auto _addChild) {
 | |
| 			for (auto& operation: _block->operations)
 | |
| 				if (auto* functionCall = get_if<CFG::FunctionCall>(&operation.operation))
 | |
| 					calls.emplace_back(functionCall);
 | |
| 			std::visit(util::GenericVisitor{
 | |
| 				[&](CFG::BasicBlock::MainExit const&) {},
 | |
| 				[&](CFG::BasicBlock::Jump const& _jump)
 | |
| 				{
 | |
| 					_addChild(_jump.target);
 | |
| 				},
 | |
| 				[&](CFG::BasicBlock::ConditionalJump const& _conditionalJump)
 | |
| 				{
 | |
| 					_addChild(_conditionalJump.zero);
 | |
| 					_addChild(_conditionalJump.nonZero);
 | |
| 				},
 | |
| 				[&](CFG::BasicBlock::FunctionReturn const&) {},
 | |
| 				[&](CFG::BasicBlock::Terminated const&)	{},
 | |
| 			}, _block->exit);
 | |
| 		});
 | |
| 		return calls;
 | |
| 	};
 | |
| 	for (auto& functionInfo: _cfg.functionInfo | ranges::views::values)
 | |
| 		for (CFG::FunctionCall* call: findCalls(functionInfo.entry))
 | |
| 		{
 | |
| 			util::BreadthFirstSearch<CFG::FunctionCall*> breadthFirstSearch{{call}};
 | |
| 			breadthFirstSearch.run([&](CFG::FunctionCall* _call, auto _addChild) {
 | |
| 				auto& calledFunctionInfo = _cfg.functionInfo.at(&_call->function.get());
 | |
| 				if (&calledFunctionInfo == &functionInfo)
 | |
| 				{
 | |
| 					call->recursive = true;
 | |
| 					breadthFirstSearch.abort();
 | |
| 					return;
 | |
| 				}
 | |
| 				for (CFG::FunctionCall* nestedCall: findCalls(_cfg.functionInfo.at(&_call->function.get()).entry))
 | |
| 					_addChild(nestedCall);
 | |
| 			});
 | |
| 		}
 | |
| }
 | |
| }
 | |
| 
 | |
| std::unique_ptr<CFG> ControlFlowGraphBuilder::build(
 | |
| 	AsmAnalysisInfo const& _analysisInfo,
 | |
| 	Dialect const& _dialect,
 | |
| 	Block const& _block
 | |
| )
 | |
| {
 | |
| 	auto result = std::make_unique<CFG>();
 | |
| 	result->entry = &result->makeBlock(debugDataOf(_block));
 | |
| 
 | |
| 	ControlFlowGraphBuilder builder(*result, _analysisInfo, _dialect);
 | |
| 	builder.m_currentBlock = result->entry;
 | |
| 	builder(_block);
 | |
| 
 | |
| 	cleanUnreachable(*result);
 | |
| 	markRecursiveCalls(*result);
 | |
| 
 | |
| 	// TODO: It might be worthwhile to run some further simplifications on the graph itself here.
 | |
| 	// E.g. if there is a jump to a node that has the jumping node as its only entry, the nodes can be fused, etc.
 | |
| 
 | |
| 	return result;
 | |
| }
 | |
| 
 | |
| ControlFlowGraphBuilder::ControlFlowGraphBuilder(
 | |
| 	CFG& _graph,
 | |
| 	AsmAnalysisInfo const& _analysisInfo,
 | |
| 	Dialect const& _dialect
 | |
| ):
 | |
| 	m_graph(_graph),
 | |
| 	m_info(_analysisInfo),
 | |
| 	m_dialect(_dialect)
 | |
| {
 | |
| }
 | |
| 
 | |
| StackSlot ControlFlowGraphBuilder::operator()(Literal const& _literal)
 | |
| {
 | |
| 	return LiteralSlot{valueOfLiteral(_literal), _literal.debugData};
 | |
| }
 | |
| 
 | |
| StackSlot ControlFlowGraphBuilder::operator()(Identifier const& _identifier)
 | |
| {
 | |
| 	return VariableSlot{lookupVariable(_identifier.name), _identifier.debugData};
 | |
| }
 | |
| 
 | |
| StackSlot ControlFlowGraphBuilder::operator()(Expression const& _expression)
 | |
| {
 | |
| 	return std::visit(*this, _expression);
 | |
| }
 | |
| 
 | |
| StackSlot ControlFlowGraphBuilder::operator()(FunctionCall const& _call)
 | |
| {
 | |
| 	Stack const& output = visitFunctionCall(_call);
 | |
| 	yulAssert(output.size() == 1, "");
 | |
| 	return output.front();
 | |
| }
 | |
| 
 | |
| void ControlFlowGraphBuilder::operator()(VariableDeclaration const& _varDecl)
 | |
| {
 | |
| 	yulAssert(m_currentBlock, "");
 | |
| 	auto declaredVariables = _varDecl.variables | ranges::views::transform([&](TypedName const& _var) {
 | |
| 		return VariableSlot{lookupVariable(_var.name), _var.debugData};
 | |
| 	}) | ranges::to<vector<VariableSlot>>;
 | |
| 	Stack input;
 | |
| 	if (_varDecl.value)
 | |
| 		input = visitAssignmentRightHandSide(*_varDecl.value, declaredVariables.size());
 | |
| 	else
 | |
| 		input = Stack(_varDecl.variables.size(), LiteralSlot{0, _varDecl.debugData});
 | |
| 	m_currentBlock->operations.emplace_back(CFG::Operation{
 | |
| 		std::move(input),
 | |
| 		declaredVariables | ranges::to<Stack>,
 | |
| 		CFG::Assignment{_varDecl.debugData, declaredVariables}
 | |
| 	});
 | |
| }
 | |
| void ControlFlowGraphBuilder::operator()(Assignment const& _assignment)
 | |
| {
 | |
| 	auto assignedVariables = _assignment.variableNames | ranges::views::transform([&](Identifier const& _var) {
 | |
| 		return VariableSlot{lookupVariable(_var.name), _var.debugData};
 | |
| 	}) | ranges::to<vector<VariableSlot>>;
 | |
| 
 | |
| 	yulAssert(m_currentBlock, "");
 | |
| 	m_currentBlock->operations.emplace_back(CFG::Operation{
 | |
| 		// input
 | |
| 		visitAssignmentRightHandSide(*_assignment.value, assignedVariables.size()),
 | |
| 		// output
 | |
| 		assignedVariables | ranges::to<Stack>,
 | |
| 		// operation
 | |
| 		CFG::Assignment{_assignment.debugData, assignedVariables}
 | |
| 	});
 | |
| }
 | |
| void ControlFlowGraphBuilder::operator()(ExpressionStatement const& _exprStmt)
 | |
| {
 | |
| 	yulAssert(m_currentBlock, "");
 | |
| 	std::visit(util::GenericVisitor{
 | |
| 		[&](FunctionCall const& _call) {
 | |
| 			Stack const& output = visitFunctionCall(_call);
 | |
| 			yulAssert(output.empty(), "");
 | |
| 		},
 | |
| 		[&](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<FunctionCall>(&_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)
 | |
| {
 | |
| 	ScopedSaveAndRestore saveScope(m_scope, m_info.scopes.at(&_block).get());
 | |
| 	for (auto const& statement: _block.statements)
 | |
| 		if (auto const* function = get_if<FunctionDefinition>(&statement))
 | |
| 			registerFunction(*function);
 | |
| 	for (auto const& statement: _block.statements)
 | |
| 		std::visit(*this, statement);
 | |
| }
 | |
| 
 | |
| 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);
 | |
| 	m_currentBlock = &ifBranch;
 | |
| 	(*this)(_if.body);
 | |
| 	jump(debugDataOf(_if.body), afterIf);
 | |
| }
 | |
| 
 | |
| void ControlFlowGraphBuilder::operator()(Switch const& _switch)
 | |
| {
 | |
| 	yulAssert(m_currentBlock, "");
 | |
| 	shared_ptr<DebugData const> preSwitchDebugData = debugDataOf(_switch);
 | |
| 
 | |
| 	auto ghostVariableId = m_graph.ghostVariables.size();
 | |
| 	YulString ghostVariableName("GHOST[" + to_string(ghostVariableId) + "]");
 | |
| 	auto& ghostVar = m_graph.ghostVariables.emplace_back(Scope::Variable{""_yulstring, ghostVariableName});
 | |
| 
 | |
| 	// Artificially generate:
 | |
| 	// let <ghostVariable> := <switchExpression>
 | |
| 	VariableSlot ghostVarSlot{ghostVar, debugDataOf(*_switch.expression)};
 | |
| 	m_currentBlock->operations.emplace_back(CFG::Operation{
 | |
| 		Stack{std::visit(*this, *_switch.expression)},
 | |
| 		Stack{ghostVarSlot},
 | |
| 		CFG::Assignment{_switch.debugData, {ghostVarSlot}}
 | |
| 	});
 | |
| 
 | |
| 	BuiltinFunction const* equalityBuiltin = m_dialect.equalityFunction({});
 | |
| 	yulAssert(equalityBuiltin, "");
 | |
| 
 | |
| 	// Artificially generate:
 | |
| 	// eq(<literal>, <ghostVariable>)
 | |
| 	auto makeValueCompare = [&](Case const& _case) {
 | |
| 		yul::FunctionCall const& ghostCall = m_graph.ghostCalls.emplace_back(yul::FunctionCall{
 | |
| 			debugDataOf(_case),
 | |
| 			yul::Identifier{{}, "eq"_yulstring},
 | |
| 			{*_case.value, Identifier{{}, ghostVariableName}}
 | |
| 		});
 | |
| 		CFG::Operation& operation = m_currentBlock->operations.emplace_back(CFG::Operation{
 | |
| 			Stack{ghostVarSlot, LiteralSlot{valueOfLiteral(*_case.value), debugDataOf(*_case.value)}},
 | |
| 			Stack{TemporarySlot{ghostCall, 0}},
 | |
| 			CFG::BuiltinCall{debugDataOf(_case), *equalityBuiltin, ghostCall, 2},
 | |
| 		});
 | |
| 		return operation.output.front();
 | |
| 	};
 | |
| 	CFG::BasicBlock& afterSwitch = m_graph.makeBlock(preSwitchDebugData);
 | |
| 	yulAssert(!_switch.cases.empty(), "");
 | |
| 	for (auto const& switchCase: _switch.cases | ranges::views::drop_last(1))
 | |
| 	{
 | |
| 		yulAssert(switchCase.value, "");
 | |
| 		auto& caseBranch = m_graph.makeBlock(debugDataOf(switchCase.body));
 | |
| 		auto& elseBranch = m_graph.makeBlock(debugDataOf(_switch));
 | |
| 		makeConditionalJump(debugDataOf(switchCase), makeValueCompare(switchCase), caseBranch, elseBranch);
 | |
| 		m_currentBlock = &caseBranch;
 | |
| 		(*this)(switchCase.body);
 | |
| 		jump(debugDataOf(switchCase.body), afterSwitch);
 | |
| 		m_currentBlock = &elseBranch;
 | |
| 	}
 | |
| 	Case const& switchCase = _switch.cases.back();
 | |
| 	if (switchCase.value)
 | |
| 	{
 | |
| 		CFG::BasicBlock& caseBranch = m_graph.makeBlock(debugDataOf(switchCase.body));
 | |
| 		makeConditionalJump(debugDataOf(switchCase), makeValueCompare(switchCase), caseBranch, afterSwitch);
 | |
| 		m_currentBlock = &caseBranch;
 | |
| 	}
 | |
| 	(*this)(switchCase.body);
 | |
| 	jump(debugDataOf(switchCase.body), afterSwitch);
 | |
| }
 | |
| 
 | |
| void ControlFlowGraphBuilder::operator()(ForLoop const& _loop)
 | |
| {
 | |
| 	shared_ptr<DebugData const> preLoopDebugData = debugDataOf(_loop);
 | |
| 	ScopedSaveAndRestore scopeRestore(m_scope, m_info.scopes.at(&_loop.pre).get());
 | |
| 	(*this)(_loop.pre);
 | |
| 
 | |
| 	std::optional<bool> constantCondition;
 | |
| 	if (auto const* literalCondition = get_if<yul::Literal>(_loop.condition.get()))
 | |
| 		constantCondition = valueOfLiteral(*literalCondition) != 0;
 | |
| 
 | |
| 	CFG::BasicBlock& loopCondition = m_graph.makeBlock(debugDataOf(*_loop.condition));
 | |
| 	CFG::BasicBlock& loopBody = m_graph.makeBlock(debugDataOf(_loop.body));
 | |
| 	CFG::BasicBlock& post = m_graph.makeBlock(debugDataOf(_loop.post));
 | |
| 	CFG::BasicBlock& afterLoop = m_graph.makeBlock(preLoopDebugData);
 | |
| 
 | |
| 	ScopedSaveAndRestore scopedSaveAndRestore(m_forLoopInfo, ForLoopInfo{afterLoop, post});
 | |
| 
 | |
| 	if (constantCondition.has_value())
 | |
| 	{
 | |
| 		if (*constantCondition)
 | |
| 		{
 | |
| 			jump(debugDataOf(_loop.pre), loopBody);
 | |
| 			(*this)(_loop.body);
 | |
| 			jump(debugDataOf(_loop.body), post);
 | |
| 			(*this)(_loop.post);
 | |
| 			jump(debugDataOf(_loop.post), loopBody, true);
 | |
| 		}
 | |
| 		else
 | |
| 			jump(debugDataOf(_loop.pre), afterLoop);
 | |
| 	}
 | |
| 	else
 | |
| 	{
 | |
| 		jump(debugDataOf(_loop.pre), loopCondition);
 | |
| 		makeConditionalJump(debugDataOf(*_loop.condition), std::visit(*this, *_loop.condition), loopBody, afterLoop);
 | |
| 		m_currentBlock = &loopBody;
 | |
| 		(*this)(_loop.body);
 | |
| 		jump(debugDataOf(_loop.body), post);
 | |
| 		(*this)(_loop.post);
 | |
| 		jump(debugDataOf(_loop.post), loopCondition, true);
 | |
| 	}
 | |
| 
 | |
| 	m_currentBlock = &afterLoop;
 | |
| }
 | |
| 
 | |
| void ControlFlowGraphBuilder::operator()(Break const& _break)
 | |
| {
 | |
| 	yulAssert(m_forLoopInfo.has_value(), "");
 | |
| 	jump(debugDataOf(_break), m_forLoopInfo->afterLoop);
 | |
| 	m_currentBlock = &m_graph.makeBlock(debugDataOf(*m_currentBlock));
 | |
| }
 | |
| 
 | |
| void ControlFlowGraphBuilder::operator()(Continue const& _continue)
 | |
| {
 | |
| 	yulAssert(m_forLoopInfo.has_value(), "");
 | |
| 	jump(debugDataOf(_continue), m_forLoopInfo->post);
 | |
| 	m_currentBlock = &m_graph.makeBlock(debugDataOf(*m_currentBlock));
 | |
| }
 | |
| 
 | |
| // '_leave' and '__leave' are reserved in VisualStudio
 | |
| void ControlFlowGraphBuilder::operator()(Leave const& leave_)
 | |
| {
 | |
| 	yulAssert(m_currentFunction.has_value(), "");
 | |
| 	m_currentBlock->exit = CFG::BasicBlock::FunctionReturn{debugDataOf(leave_), *m_currentFunction};
 | |
| 	m_currentBlock = &m_graph.makeBlock(debugDataOf(*m_currentBlock));
 | |
| }
 | |
| 
 | |
| void ControlFlowGraphBuilder::operator()(FunctionDefinition const& _function)
 | |
| {
 | |
| 	yulAssert(m_scope, "");
 | |
| 	yulAssert(m_scope->identifiers.count(_function.name), "");
 | |
| 	Scope::Function& function = std::get<Scope::Function>(m_scope->identifiers.at(_function.name));
 | |
| 	m_graph.functions.emplace_back(&function);
 | |
| 
 | |
| 	CFG::FunctionInfo& functionInfo = m_graph.functionInfo.at(&function);
 | |
| 
 | |
| 	ControlFlowGraphBuilder builder{m_graph, m_info, m_dialect};
 | |
| 	builder.m_currentFunction = &functionInfo;
 | |
| 	builder.m_currentBlock = functionInfo.entry;
 | |
| 	builder(_function.body);
 | |
| 	builder.m_currentBlock->exit = CFG::BasicBlock::FunctionReturn{debugDataOf(_function), &functionInfo};
 | |
| }
 | |
| 
 | |
| void ControlFlowGraphBuilder::registerFunction(FunctionDefinition const& _function)
 | |
| {
 | |
| 	yulAssert(m_scope, "");
 | |
| 	yulAssert(m_scope->identifiers.count(_function.name), "");
 | |
| 	Scope::Function& function = std::get<Scope::Function>(m_scope->identifiers.at(_function.name));
 | |
| 
 | |
| 	yulAssert(m_info.scopes.at(&_function.body), "");
 | |
| 	Scope* virtualFunctionScope = m_info.scopes.at(m_info.virtualBlocks.at(&_function).get()).get();
 | |
| 	yulAssert(virtualFunctionScope, "");
 | |
| 
 | |
| 	bool inserted = m_graph.functionInfo.emplace(std::make_pair(&function, CFG::FunctionInfo{
 | |
| 		_function.debugData,
 | |
| 		function,
 | |
| 		&m_graph.makeBlock(debugDataOf(_function.body)),
 | |
| 		_function.parameters | ranges::views::transform([&](auto const& _param) {
 | |
| 			return VariableSlot{
 | |
| 				std::get<Scope::Variable>(virtualFunctionScope->identifiers.at(_param.name)),
 | |
| 				_param.debugData
 | |
| 			};
 | |
| 		}) | ranges::to<vector>,
 | |
| 		_function.returnVariables | ranges::views::transform([&](auto const& _retVar) {
 | |
| 			return VariableSlot{
 | |
| 				std::get<Scope::Variable>(virtualFunctionScope->identifiers.at(_retVar.name)),
 | |
| 				_retVar.debugData
 | |
| 			};
 | |
| 		}) | ranges::to<vector>
 | |
| 	})).second;
 | |
| 	yulAssert(inserted);
 | |
| }
 | |
| 
 | |
| Stack const& ControlFlowGraphBuilder::visitFunctionCall(FunctionCall const& _call)
 | |
| {
 | |
| 	yulAssert(m_scope, "");
 | |
| 	yulAssert(m_currentBlock, "");
 | |
| 
 | |
| 	if (BuiltinFunction const* builtin = m_dialect.builtin(_call.functionName.name))
 | |
| 	{
 | |
| 		Stack inputs;
 | |
| 		for (auto&& [idx, arg]: _call.arguments | ranges::views::enumerate | ranges::views::reverse)
 | |
| 			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{
 | |
| 			// input
 | |
| 			std::move(inputs),
 | |
| 			// output
 | |
| 			ranges::views::iota(0u, builtin->returns.size()) | ranges::views::transform([&](size_t _i) {
 | |
| 				return TemporarySlot{_call, _i};
 | |
| 			}) | ranges::to<Stack>,
 | |
| 			// operation
 | |
| 			move(builtinCall)
 | |
| 		}).output;
 | |
| 	}
 | |
| 	else
 | |
| 	{
 | |
| 		Scope::Function const& function = lookupFunction(_call.functionName.name);
 | |
| 		Stack inputs{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{
 | |
| 			// input
 | |
| 			std::move(inputs),
 | |
| 			// output
 | |
| 			ranges::views::iota(0u, function.returns.size()) | ranges::views::transform([&](size_t _i) {
 | |
| 				return TemporarySlot{_call, _i};
 | |
| 			}) | ranges::to<Stack>,
 | |
| 			// operation
 | |
| 			CFG::FunctionCall{_call.debugData, function, _call}
 | |
| 		}).output;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| Stack ControlFlowGraphBuilder::visitAssignmentRightHandSide(Expression const& _expression, size_t _expectedSlotCount)
 | |
| {
 | |
| 	return std::visit(util::GenericVisitor{
 | |
| 		[&](FunctionCall const& _call) -> Stack {
 | |
| 			Stack const& output = visitFunctionCall(_call);
 | |
| 			yulAssert(_expectedSlotCount == output.size(), "");
 | |
| 			return output;
 | |
| 		},
 | |
| 		[&](auto const& _identifierOrLiteral) -> Stack {
 | |
| 			yulAssert(_expectedSlotCount == 1, "");
 | |
| 			return {(*this)(_identifierOrLiteral)};
 | |
| 		}
 | |
| 	}, _expression);
 | |
| }
 | |
| 
 | |
| Scope::Function const& ControlFlowGraphBuilder::lookupFunction(YulString _name) const
 | |
| {
 | |
| 	Scope::Function const* function = nullptr;
 | |
| 	yulAssert(m_scope->lookup(_name, util::GenericVisitor{
 | |
| 		[](Scope::Variable&) { yulAssert(false, "Expected function name."); },
 | |
| 		[&](Scope::Function& _function) { function = &_function; }
 | |
| 	}), "Function name not found.");
 | |
| 	yulAssert(function, "");
 | |
| 	return *function;
 | |
| }
 | |
| 
 | |
| Scope::Variable const& ControlFlowGraphBuilder::lookupVariable(YulString _name) const
 | |
| {
 | |
| 	yulAssert(m_scope, "");
 | |
| 	Scope::Variable const* var = nullptr;
 | |
| 	if (m_scope->lookup(_name, util::GenericVisitor{
 | |
| 		[&](Scope::Variable& _var) { var = &_var; },
 | |
| 		[](Scope::Function&)
 | |
| 		{
 | |
| 			yulAssert(false, "Function not removed during desugaring.");
 | |
| 		}
 | |
| 	}))
 | |
| 	{
 | |
| 		yulAssert(var, "");
 | |
| 		return *var;
 | |
| 	};
 | |
| 	yulAssert(false, "External identifier access unimplemented.");
 | |
| }
 | |
| 
 | |
| void ControlFlowGraphBuilder::makeConditionalJump(
 | |
| 	shared_ptr<DebugData const> _debugData,
 | |
| 	StackSlot _condition,
 | |
| 	CFG::BasicBlock& _nonZero,
 | |
| 	CFG::BasicBlock& _zero
 | |
| )
 | |
| {
 | |
| 	yulAssert(m_currentBlock, "");
 | |
| 	m_currentBlock->exit = CFG::BasicBlock::ConditionalJump{
 | |
| 		move(_debugData),
 | |
| 		move(_condition),
 | |
| 		&_nonZero,
 | |
| 		&_zero
 | |
| 	};
 | |
| 	_nonZero.entries.emplace_back(m_currentBlock);
 | |
| 	_zero.entries.emplace_back(m_currentBlock);
 | |
| 	m_currentBlock = nullptr;
 | |
| }
 | |
| 
 | |
| void ControlFlowGraphBuilder::jump(
 | |
| 	shared_ptr<DebugData const> _debugData,
 | |
| 	CFG::BasicBlock& _target,
 | |
| 	bool backwards
 | |
| )
 | |
| {
 | |
| 	yulAssert(m_currentBlock, "");
 | |
| 	m_currentBlock->exit = CFG::BasicBlock::Jump{move(_debugData), &_target, backwards};
 | |
| 	_target.entries.emplace_back(m_currentBlock);
 | |
| 	m_currentBlock = &_target;
 | |
| }
 |