Skip to content

Conversation

@chriseth
Copy link
Contributor

@chriseth chriseth commented Oct 4, 2021

No description provided.

@chriseth
Copy link
Contributor Author

chriseth commented Oct 4, 2021

This is working now apart from recursive function calls. I'm actually wondering if it would be better to finish implementing ControlFlowBuilder on the full Yul AST and then doing reachability analysis there.

@chriseth
Copy link
Contributor Author

chriseth commented Oct 4, 2021

Ok actually I think ControlFlowBuilder cannot be really used, but essentially something that actually builds a graph.

@chriseth chriseth force-pushed the controlFlowSideEffectsUserDefined branch from d908dd9 to 6a6ed4e Compare October 5, 2021 15:32
@argotorg argotorg deleted a comment from stackenbotten Oct 5, 2021
@argotorg argotorg deleted a comment from stackenbotten Oct 5, 2021
// 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.terminates)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ekpyron the above comment can now be implemented.

@chriseth chriseth marked this pull request as ready for review October 5, 2021 16:47
@chriseth chriseth force-pushed the controlFlowSideEffectsUserDefined branch from 6a6ed4e to d5a397a Compare October 5, 2021 16:59
@stackenbotten
Copy link

There was an error when running chk_coding_style for commit d5a397a680fdefabefe23289dfdb9ed0084f1a01:

Error: Trailing whitespace found:
test/libyul/controlFlowSideEffects/recursion.yul:32:// c: 
test/libyul/controlFlowSideEffects/recursion.yul:33:// d: 
test/libyul/controlFlowSideEffects/recursion.yul:35:// x: 
test/libyul/controlFlowSideEffects/recursion.yul:36:// y: 
test/libyul/controlFlowSideEffects/recursion.yul:37:// z: 

Please check that your changes are working as intended.

@chriseth chriseth force-pushed the controlFlowSideEffectsUserDefined branch from d5a397a to 6220621 Compare October 5, 2021 17:02
@chriseth chriseth force-pushed the controlFlowSideEffectsUserDefined branch from 6220621 to a1756ae Compare October 6, 2021 15:07
Comment on lines 22 to 23
#include <stack>
#include <optional>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All these includes are unnecessary, aren't they? Even set.

{
walkVector(_functionCall.arguments | ranges::views::reverse);
newConnectedNode();
m_currentNode->functionCall = {_functionCall.functionName.name};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
m_currentNode->functionCall = {_functionCall.functionName.name};
m_currentNode->functionCall = _functionCall.functionName.name;

The braces are a bit misleading for the optional type.

Comment on lines 73 to 83
ScopedSaveAndRestore breakNode(m_break, nullptr);
ScopedSaveAndRestore continueNode(m_continue, nullptr);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are these necessary?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe not if we assume the hoister, but better safe than sorry.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, okay. We could assert that m_continue and m_break are nullptr if we really want to be extra safe. Same with m_leave and m_break.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should either assert or save and restore. What would you prefer?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer the assert.

Comment on lines 34 to 48
struct ControlFlowNode
{
std::vector<ControlFlowNode const*> successors;
std::optional<YulString> functionCall;
};

struct FunctionFlow
{
ControlFlowNode const* entry;
ControlFlowNode const* exit;
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could use some documentation.

/// If true, the function contains at least one reachable branch that reverts.
bool canRevert = false;
/// If true, the function has a regular outgoing control-flow.
bool canContinue = true;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this can be replaced by willTerminate and that sounds more appropriate.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure. These three bools are essentially edges to special nodes in the control-flow graph, and I would prefer not to invert the meaning of one of them.

Also canContinue can be false due to the function being always recursive.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also canContinue can be false due to the function being always recursive.

This is not currently done, is it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment on lines 237 to 246
while (it != end)
{
ControlFlowNode const* node = *it;
if (!node->functionCall || exitKnownReachable(*node->functionCall))
{
m_pendingNodes[_functionName].erase(it);
return node;
}
++it;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be replaced by std::find, followed by an erase.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Like this? Would you prefer it?

	auto it = m_pendingNodes[_functionName].begin();
	auto end = m_pendingNodes[_functionName].end();
	it = find_if(it, end, [](ControlFlowNode const* node) {
		return !node->functionCall || exitKnownReachable(*node->functionCall);
	}
	if (it == end)
		return nullptr;
	else
	{
		ControlFlowNode const* node = *it;
		m_pendingNodes[_functionName].erase(it);
		return node;
	}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes :) But it did look better when I thought about the change.

@chriseth chriseth force-pushed the controlFlowSideEffectsUserDefined branch 6 times, most recently from 179ca43 to 35b5a68 Compare October 13, 2021 15:39
Comment on lines 73 to 83
ScopedSaveAndRestore breakNode(m_break, nullptr);
ScopedSaveAndRestore continueNode(m_continue, nullptr);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, okay. We could assert that m_continue and m_break are nullptr if we really want to be extra safe. Same with m_leave and m_break.

flow.exit = newNode();
m_currentNode = newNode();
flow.entry = m_currentNode;
newConnectedNode();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this might be redundant.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you are right.

void ControlFlowBuilder::operator()(Leave const&)
{
m_currentNode->successors.emplace_back(m_leave);
m_currentNode = newNode();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if this is really needed. Effectively, edges coming from here doesn't matter, I think.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we discussed this offline but if you don't disconnect the node, then function f() { leave revert(0, 0) } will have can revert. With dead code removal this might not matter, but I think it is still good to have.

while (progress)
{
progress = false;
for (auto const& fun: m_pendingNodes)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This may lead to undefined behaviour. The call procesFunction(...) can update m_pendingNodes in two different ways:

  1. nextProcessableNode(...) can remove the current iterator; wouldn't that lead to undefined behaviour when the current one gets incremented?
  2. recordReachabilityAndQueue can add to the beginning of m_pendingNodes. Why is it guaranteed that this node will get processed?

Would it be more appropriate to do something like while(!m_pendingNodes.empty()) { node = m_pendingNodes.front(); ...}


Also, otherwise could change to m_pendingNodes | range::views::keys)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I follow, I think this might be a misunderstanding on your part: m_pendingNodes is a map by function name - so there is one queue per function name. As long as we don't add or remove a function, the iterator here is not invalidated. I can split recordReachabilityAndQueue into two functions:

  1. one that can also add a new function
  2. one that asserts that the function is already there
    and use the first one above in line 171 and the second one in line 232

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the change. Was a misunderstanding.

Comment on lines 211 to 212
if (m_functionCalls.count(_function))
for (YulString callee: m_functionCalls.at(_function))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could replace the .count followed by .at by valueOrDefault from CommonData.h

std::map<YulString, ControlFlowSideEffects> m_functionSideEffects;
std::map<YulString, std::list<ControlFlowNode const*>> m_pendingNodes;
std::map<YulString, std::set<ControlFlowNode const*>> m_processedNodes;
std::map<YulString, std::set<YulString>> m_functionCalls;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could use documentation. Wasn't obvious that this is a set of reachable function calls inside a function.

@chriseth chriseth force-pushed the controlFlowSideEffectsUserDefined branch from c2bd1d2 to dc4e951 Compare October 14, 2021 14:59
hrkrshnn
hrkrshnn previously approved these changes Oct 14, 2021
@hrkrshnn
Copy link
Contributor

Some commits are left for squashing, can be merged afterwards.

@chriseth chriseth enabled auto-merge October 14, 2021 16:12
@chriseth chriseth merged commit 1e630fc into develop Oct 14, 2021
@chriseth chriseth deleted the controlFlowSideEffectsUserDefined branch October 14, 2021 16:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants