Skip to content

Conversation

@slyubomirsky
Copy link
Contributor

As part of #15319, this PR implements liveness analysis, which is implemented using a dataflow analysis framework similar to that described by Adrian Sampson in these lecture notes: https://www.cs.cornell.edu/courses/cs6120/2020fa/lesson/4/. See also Chapter 5 of Static Program Analysis by Møller and Schwartzbach.

This required, for good or ill, introducing quite a bit of infrastructure. Here is a summary:

  • A control flow graph representation, representing a function in terms of bindings (which maps closely to the notion of bindings within Relax's AST, but we also need to represent the body field of SeqExprs and we have to consider the condition, true branch, false branch, and the merge point at the end of an If node, so the mapping is not one-to-one).
  • An implementation of the general dataflow framework from the sources listed.
  • An implementation of liveness analysis using Sampson's approach (which ended up being very compact thanks to the dataflow framework). The liveness analysis results are given in terms of a set of live variables per node in the control flow graph. A helper function is included for mapping between the AST and the CFG.

I highly encourage review, since I am a bit nervous about introducing new infrastructure; I am especially concerned about having clear naming (I am worried about having a name so close to the dataflow pattern matching). I felt it was unavoidable in this case, because there does not seem to be any other way for us to annotate program locations with liveness information. However, I am very glad to have implemented the general dataflow framework, since it could also be used for alias analysis and likely further analyses down the line.

@slyubomirsky slyubomirsky requested a review from tqchen September 8, 2023 19:06
Copy link
Contributor

@Lunderberg Lunderberg left a comment

Choose a reason for hiding this comment

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

I really like this functionality, and there's enough partial implementations of it floating around that it would be good to consolidate them together.

The implementation looks pretty solid to me, with most of my comments focusing on usability and extensibility.

* 2. The condition expression in an If node (a "split" point)
* 3. A merge point (the variable to which an If node is bound: it is a "merge" between
* the SeqExprs in the true and false branches)
* 4. The body expression in a SeqExpr (not actually bound)
Copy link
Contributor

Choose a reason for hiding this comment

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

Does this imply that the analysis can only be applied to normalized relax expressions? If non-normalized, the body of a SeqExpr is bound to a variable in the containing BindingBlock or DataflowBlock.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, the expectation is for expressions to be normalized first.

* so use `ExtractCFG` and `GetBindingIndex` to match locations in `fn`
* to indices in the result.
*/
Array<Array<Var>> LivenessAnalysis(const Function& fn);
Copy link
Contributor

Choose a reason for hiding this comment

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

Instead of Array<Array<Var>>, could we return Map<Var, Array<Var>>? That is, a map from the variable being bound to the list of variables that are live while the value of the variable is being computed. That would avoid requiring a user of the function to know the internal indexing scheme, and most of the APIs have easy access to the Var (e.g. In a mutator that implements ExprMutator::VisitBinding).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good idea, I'll see about trying 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.

I've looked into this and what's tricky is dealing with cases like SeqExpr body values and If conditions and merge/split points. The body does not have a var associated with it and in the case of Ifs, there is more than one CFG entry associated with one binding. Var alone would not be a good key, which is why I had the indices in the first place. We could use a CFGKey object that contains this auxiliary info or otherwise keep a data structure around for the reverse mapping, potentially.

// This is an inefficient linear scan; it could be improved by keeping a map of
// SeqExprs to indices in the CFG data structure.
// That should be considered if this function poses performance issues (unlikely).
for (size_t i = 0; i < cfg->bindings.size(); i++) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Another advantage of using a relax::Var to specify the location at which that variable is being bound, we would avoid the possible future problem of the linear scan being inefficient.

* each binding in the CFG) and the second being the "output map" (the domain
* being passed *out of* the corresponding binding)
*/
std::pair<Array<ObjectRef>, Array<ObjectRef>> DataflowAnalysis(
Copy link
Contributor

Choose a reason for hiding this comment

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

Should this function be externally exposed? From the changes in this PR on its own, it looks like an implementation detail for LivenessAnalysis, but the function signature suggests that it is intended for more general use.

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're right, the use would be mainly internal. It might be good to expose it for testing, or to allow for defining analyses in Python.

TVM_DECLARE_BASE_OBJECT_INFO(ControlFlowGraphNode, Object);
};

class ControlFlowGraph : public ObjectRef {
Copy link
Contributor

Choose a reason for hiding this comment

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

Looking at this structure, I think it could be generalized to also represent a data-dependency graph, and most of the functionality would also carry over.

  • Both the predecessors in a control-flow graph are analogous to the inputs for a data-dependency graph, and both could be represented by Array<Var>.
  • Both the successors in a control-flow graph are analogous to the outputs of a data-dependency graph, and both could be represented by Array<Var>.
  • The DataflowAnalysis function would operate identically in both cases, either flowing things that are known at a specific time for a control-flow graph, or flowing things that are known about a specific value for a data-dependency graph.

What are your thoughts on generalizing the utility? I think the main drawback would be if there's a fundamental assumption made about the graph structure that only holds for one of the two, but they look like they might be similar enough to have lots of overlap.

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 a dependency graph is more specific than a CFG, so I would have to think about whether the same analyses would work on one. It's worth further thought.

// 1 predecessor: A branch in an If node (no merge needed)
// 2 predecessors: The merge block after an If node (merge needed)
// (Analogous for successors in backward analysis)
inputs->operator[](idx) = (prev.size() == 0) ? init
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: If this is declared as std::vector<ObjectRef>& inputs = (forward)? out_map : in_map;, then the LHS of the assignment becomes inputs[idx] instead of inputs->operator[](idx).

@slyubomirsky
Copy link
Contributor Author

Thanks for the comments, @Lunderberg. I may revisit this implementation for automatically extracting DataflowBlocks.

@slyubomirsky
Copy link
Contributor Author

It looks like it wasn't needed for #16204 thanks to CanonicalizeBindings, so I guess there's no immediate need for it still. We can continue discussing if we want this analysis for something else.

@junrushao junrushao force-pushed the unity branch 2 times, most recently from c95d45f to 45eeb8c Compare December 18, 2023 21:00
@tqchen tqchen deleted the branch apache:unity March 29, 2024 12:18
@tqchen tqchen closed this Mar 29, 2024
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.

3 participants