Skip to content

Commit 946c8f1

Browse files
cxy-1993DanielCChen
authored andcommitted
[mlir] [dataflow] unify semantics of program point (llvm#110344)
The concept of a 'program point' in the original data flow framework is ambiguous. It can refer to either an operation or a block itself. This representation has different interpretations in forward and backward data-flow analysis. In forward data-flow analysis, the program point of an operation represents the state after the operation, while in backward data flow analysis, it represents the state before the operation. When using forward or backward data-flow analysis, it is crucial to carefully handle this distinction to ensure correctness. This patch refactors the definition of program point, unifying the interpretation of program points in both forward and backward data-flow analysis. How to integrate this patch? For dense forward data-flow analysis and other analysis (except dense backward data-flow analysis), the program point corresponding to the original operation can be obtained by `getProgramPointAfter(op)`, and the program point corresponding to the original block can be obtained by `getProgramPointBefore(block)`. For dense backward data-flow analysis, the program point corresponding to the original operation can be obtained by `getProgramPointBefore(op)`, and the program point corresponding to the original block can be obtained by `getProgramPointAfter(block)`. NOTE: If you need to get the lattice of other data-flow analyses in dense backward data-flow analysis, you should still use the dense forward data-flow approach. For example, to get the Executable state of a block in dense backward data-flow analysis and add the dependency of the current operation, you should write: ``getOrCreateFor<Executable>(getProgramPointBefore(op), getProgramPointBefore(block))`` In case above, we use getProgramPointBefore(op) because the analysis we rely on is dense backward data-flow, and we use getProgramPointBefore(block) because the lattice we query is the result of a non-dense backward data flow computation. related dsscussion: https://discourse.llvm.org/t/rfc-unify-the-semantics-of-program-points/80671/8 corresponding PSA: https://discourse.llvm.org/t/psa-program-point-semantics-change/81479
1 parent 4511f1d commit 946c8f1

18 files changed

+491
-306
lines changed

flang/lib/Optimizer/Transforms/StackArrays.cpp

+13-11
Original file line numberDiff line numberDiff line change
@@ -375,7 +375,7 @@ mlir::LogicalResult AllocationAnalysis::visitOperation(
375375
}
376376
} else if (mlir::isa<fir::ResultOp>(op)) {
377377
mlir::Operation *parent = op->getParentOp();
378-
LatticePoint *parentLattice = getLattice(parent);
378+
LatticePoint *parentLattice = getLattice(getProgramPointAfter(parent));
379379
assert(parentLattice);
380380
mlir::ChangeResult parentChanged = parentLattice->join(*after);
381381
propagateIfChanged(parentLattice, parentChanged);
@@ -396,28 +396,29 @@ void AllocationAnalysis::setToEntryState(LatticePoint *lattice) {
396396
/// Mostly a copy of AbstractDenseLattice::processOperation - the difference
397397
/// being that call operations are passed through to the transfer function
398398
mlir::LogicalResult AllocationAnalysis::processOperation(mlir::Operation *op) {
399+
mlir::ProgramPoint *point = getProgramPointAfter(op);
399400
// If the containing block is not executable, bail out.
400-
if (!getOrCreateFor<mlir::dataflow::Executable>(op, op->getBlock())->isLive())
401+
if (op->getBlock() != nullptr &&
402+
!getOrCreateFor<mlir::dataflow::Executable>(
403+
point, getProgramPointBefore(op->getBlock()))
404+
->isLive())
401405
return mlir::success();
402406

403407
// Get the dense lattice to update
404-
mlir::dataflow::AbstractDenseLattice *after = getLattice(op);
408+
mlir::dataflow::AbstractDenseLattice *after = getLattice(point);
405409

406410
// If this op implements region control-flow, then control-flow dictates its
407411
// transfer function.
408412
if (auto branch = mlir::dyn_cast<mlir::RegionBranchOpInterface>(op)) {
409-
visitRegionBranchOperation(op, branch, after);
413+
visitRegionBranchOperation(point, branch, after);
410414
return mlir::success();
411415
}
412416

413417
// pass call operations through to the transfer function
414418

415419
// Get the dense state before the execution of the op.
416-
const mlir::dataflow::AbstractDenseLattice *before;
417-
if (mlir::Operation *prev = op->getPrevNode())
418-
before = getLatticeFor(op, prev);
419-
else
420-
before = getLatticeFor(op, op->getBlock());
420+
const mlir::dataflow::AbstractDenseLattice *before =
421+
getLatticeFor(point, getProgramPointBefore(op));
421422

422423
/// Invoke the operation transfer function
423424
return visitOperationImpl(op, *before, after);
@@ -452,9 +453,10 @@ StackArraysAnalysisWrapper::analyseFunction(mlir::Operation *func) {
452453
return mlir::failure();
453454
}
454455

455-
LatticePoint point{func};
456+
LatticePoint point{solver.getProgramPointAfter(func)};
456457
auto joinOperationLattice = [&](mlir::Operation *op) {
457-
const LatticePoint *lattice = solver.lookupState<LatticePoint>(op);
458+
const LatticePoint *lattice =
459+
solver.lookupState<LatticePoint>(solver.getProgramPointAfter(op));
458460
// there will be no lattice for an unreachable block
459461
if (lattice)
460462
(void)point.join(*lattice);

mlir/include/mlir/Analysis/DataFlow/DeadCodeAnalysis.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ class DeadCodeAnalysis : public DataFlowAnalysis {
182182

183183
/// Visit an operation with control-flow semantics and deduce which of its
184184
/// successors are live.
185-
LogicalResult visit(ProgramPoint point) override;
185+
LogicalResult visit(ProgramPoint *point) override;
186186

187187
private:
188188
/// Find and mark symbol callables with potentially unknown callsites as

mlir/include/mlir/Analysis/DataFlow/DenseAnalysis.h

+36-40
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,7 @@ enum class CallControlFlowAction { EnterCallee, ExitCallee, ExternalCallee };
3636
//===----------------------------------------------------------------------===//
3737

3838
/// This class represents a dense lattice. A dense lattice is attached to
39-
/// operations to represent the program state after their execution or to blocks
40-
/// to represent the program state at the beginning of the block. A dense
39+
/// program point to represent the program state at the program point.
4140
/// lattice is propagated through the IR by dense data-flow analysis.
4241
class AbstractDenseLattice : public AnalysisState {
4342
public:
@@ -59,15 +58,13 @@ class AbstractDenseLattice : public AnalysisState {
5958
//===----------------------------------------------------------------------===//
6059

6160
/// Base class for dense forward data-flow analyses. Dense data-flow analysis
62-
/// attaches a lattice between the execution of operations and implements a
63-
/// transfer function from the lattice before each operation to the lattice
64-
/// after. The lattice contains information about the state of the program at
65-
/// that point.
61+
/// attaches a lattice to program points and implements a transfer function from
62+
/// the lattice before each operation to the lattice after. The lattice contains
63+
/// information about the state of the program at that program point.
6664
///
67-
/// In this implementation, a lattice attached to an operation represents the
68-
/// state of the program after its execution, and a lattice attached to block
69-
/// represents the state of the program right before it starts executing its
70-
/// body.
65+
/// Visit a program point in forward dense data-flow analysis will invoke the
66+
/// transfer function of the operation preceding the program point iterator.
67+
/// Visit a program point at the begining of block will visit the block itself.
7168
class AbstractDenseForwardDataFlowAnalysis : public DataFlowAnalysis {
7269
public:
7370
using DataFlowAnalysis::DataFlowAnalysis;
@@ -76,13 +73,14 @@ class AbstractDenseForwardDataFlowAnalysis : public DataFlowAnalysis {
7673
/// may modify the program state; that is, every operation and block.
7774
LogicalResult initialize(Operation *top) override;
7875

79-
/// Visit a program point that modifies the state of the program. If this is a
80-
/// block, then the state is propagated from control-flow predecessors or
81-
/// callsites. If this is a call operation or region control-flow operation,
82-
/// then the state after the execution of the operation is set by control-flow
83-
/// or the callgraph. Otherwise, this function invokes the operation transfer
84-
/// function.
85-
LogicalResult visit(ProgramPoint point) override;
76+
/// Visit a program point that modifies the state of the program. If the
77+
/// program point is at the beginning of a block, then the state is propagated
78+
/// from control-flow predecessors or callsites. If the operation before
79+
/// program point iterator is a call operation or region control-flow
80+
/// operation, then the state after the execution of the operation is set by
81+
/// control-flow or the callgraph. Otherwise, this function invokes the
82+
/// operation transfer function before the program point iterator.
83+
LogicalResult visit(ProgramPoint *point) override;
8684

8785
protected:
8886
/// Propagate the dense lattice before the execution of an operation to the
@@ -91,15 +89,14 @@ class AbstractDenseForwardDataFlowAnalysis : public DataFlowAnalysis {
9189
const AbstractDenseLattice &before,
9290
AbstractDenseLattice *after) = 0;
9391

94-
/// Get the dense lattice after the execution of the given lattice anchor.
92+
/// Get the dense lattice on the given lattice anchor.
9593
virtual AbstractDenseLattice *getLattice(LatticeAnchor anchor) = 0;
9694

97-
/// Get the dense lattice after the execution of the given program point and
98-
/// add it as a dependency to a lattice anchor. That is, every time the
99-
/// lattice after anchor is updated, the dependent program point must be
100-
/// visited, and the newly triggered visit might update the lattice after
101-
/// dependent.
102-
const AbstractDenseLattice *getLatticeFor(ProgramPoint dependent,
95+
/// Get the dense lattice on the given lattice anchor and add dependent as its
96+
/// dependency. That is, every time the lattice after anchor is updated, the
97+
/// dependent program point must be visited, and the newly triggered visit
98+
/// might update the lattice on dependent.
99+
const AbstractDenseLattice *getLatticeFor(ProgramPoint *dependent,
103100
LatticeAnchor anchor);
104101

105102
/// Set the dense lattice at control flow entry point and propagate an update
@@ -153,7 +150,7 @@ class AbstractDenseForwardDataFlowAnalysis : public DataFlowAnalysis {
153150
/// Visit a program point within a region branch operation with predecessors
154151
/// in it. This can either be an entry block of one of the regions of the
155152
/// parent operation itself.
156-
void visitRegionBranchOperation(ProgramPoint point,
153+
void visitRegionBranchOperation(ProgramPoint *point,
157154
RegionBranchOpInterface branch,
158155
AbstractDenseLattice *after);
159156

@@ -294,14 +291,12 @@ class DenseForwardDataFlowAnalysis
294291
//===----------------------------------------------------------------------===//
295292

296293
/// Base class for dense backward dataflow analyses. Such analyses attach a
297-
/// lattice between the execution of operations and implement a transfer
298-
/// function from the lattice after the operation ot the lattice before it, thus
299-
/// propagating backward.
294+
/// lattice to program point and implement a transfer function from the lattice
295+
/// after the operation to the lattice before it, thus propagating backward.
300296
///
301-
/// In this implementation, a lattice attached to an operation represents the
302-
/// state of the program before its execution, and a lattice attached to a block
303-
/// represents the state of the program before the end of the block, i.e., after
304-
/// its terminator.
297+
/// Visit a program point in dense backward data-flow analysis will invoke the
298+
/// transfer function of the operation following the program point iterator.
299+
/// Visit a program point at the end of block will visit the block itself.
305300
class AbstractDenseBackwardDataFlowAnalysis : public DataFlowAnalysis {
306301
public:
307302
/// Construct the analysis in the given solver. Takes a symbol table
@@ -321,9 +316,9 @@ class AbstractDenseBackwardDataFlowAnalysis : public DataFlowAnalysis {
321316
/// operations, the state is propagated using the transfer function
322317
/// (visitOperation).
323318
///
324-
/// Note: the transfer function is currently *not* invoked for operations with
325-
/// region or call interface, but *is* invoked for block terminators.
326-
LogicalResult visit(ProgramPoint point) override;
319+
/// Note: the transfer function is currently *not* invoked before operations
320+
/// with region or call interface, but *is* invoked before block terminators.
321+
LogicalResult visit(ProgramPoint *point) override;
327322

328323
protected:
329324
/// Propagate the dense lattice after the execution of an operation to the
@@ -337,10 +332,11 @@ class AbstractDenseBackwardDataFlowAnalysis : public DataFlowAnalysis {
337332
/// block.
338333
virtual AbstractDenseLattice *getLattice(LatticeAnchor anchor) = 0;
339334

340-
/// Get the dense lattice before the execution of the program point in
341-
/// `anchor` and declare that the `dependent` program point must be updated
342-
/// every time `point` is.
343-
const AbstractDenseLattice *getLatticeFor(ProgramPoint dependent,
335+
/// Get the dense lattice on the given lattice anchor and add dependent as its
336+
/// dependency. That is, every time the lattice after anchor is updated, the
337+
/// dependent program point must be visited, and the newly triggered visit
338+
/// might update the lattice before dependent.
339+
const AbstractDenseLattice *getLatticeFor(ProgramPoint *dependent,
344340
LatticeAnchor anchor);
345341

346342
/// Set the dense lattice before at the control flow exit point and propagate
@@ -400,7 +396,7 @@ class AbstractDenseBackwardDataFlowAnalysis : public DataFlowAnalysis {
400396
/// (from which the state is propagated) in or after it. `regionNo` indicates
401397
/// the region that contains the successor, `nullopt` indicating the successor
402398
/// of the branch operation itself.
403-
void visitRegionBranchOperation(ProgramPoint point,
399+
void visitRegionBranchOperation(ProgramPoint *point,
404400
RegionBranchOpInterface branch,
405401
RegionBranchPoint branchPoint,
406402
AbstractDenseLattice *before);

mlir/include/mlir/Analysis/DataFlow/SparseAnalysis.h

+22-13
Original file line numberDiff line numberDiff line change
@@ -179,18 +179,22 @@ class Lattice : public AbstractSparseLattice {
179179
/// operands to the lattices of the results. This analysis will propagate
180180
/// lattices across control-flow edges and the callgraph using liveness
181181
/// information.
182+
///
183+
/// Visit a program point in sparse forward data-flow analysis will invoke the
184+
/// transfer function of the operation preceding the program point iterator.
185+
/// Visit a program point at the begining of block will visit the block itself.
182186
class AbstractSparseForwardDataFlowAnalysis : public DataFlowAnalysis {
183187
public:
184188
/// Initialize the analysis by visiting every owner of an SSA value: all
185189
/// operations and blocks.
186190
LogicalResult initialize(Operation *top) override;
187191

188-
/// Visit a program point. If this is a block and all control-flow
189-
/// predecessors or callsites are known, then the arguments lattices are
190-
/// propagated from them. If this is a call operation or an operation with
191-
/// region control-flow, then its result lattices are set accordingly.
192-
/// Otherwise, the operation transfer function is invoked.
193-
LogicalResult visit(ProgramPoint point) override;
192+
/// Visit a program point. If this is at beginning of block and all
193+
/// control-flow predecessors or callsites are known, then the arguments
194+
/// lattices are propagated from them. If this is after call operation or an
195+
/// operation with region control-flow, then its result lattices are set
196+
/// accordingly. Otherwise, the operation transfer function is invoked.
197+
LogicalResult visit(ProgramPoint *point) override;
194198

195199
protected:
196200
explicit AbstractSparseForwardDataFlowAnalysis(DataFlowSolver &solver);
@@ -221,7 +225,7 @@ class AbstractSparseForwardDataFlowAnalysis : public DataFlowAnalysis {
221225

222226
/// Get a read-only lattice element for a value and add it as a dependency to
223227
/// a program point.
224-
const AbstractSparseLattice *getLatticeElementFor(ProgramPoint point,
228+
const AbstractSparseLattice *getLatticeElementFor(ProgramPoint *point,
225229
Value value);
226230

227231
/// Set the given lattice element(s) at control flow entry point(s).
@@ -251,7 +255,8 @@ class AbstractSparseForwardDataFlowAnalysis : public DataFlowAnalysis {
251255
/// operation `branch`, which can either be the entry block of one of the
252256
/// regions or the parent operation itself, and set either the argument or
253257
/// parent result lattices.
254-
void visitRegionSuccessors(ProgramPoint point, RegionBranchOpInterface branch,
258+
void visitRegionSuccessors(ProgramPoint *point,
259+
RegionBranchOpInterface branch,
255260
RegionBranchPoint successor,
256261
ArrayRef<AbstractSparseLattice *> lattices);
257262
};
@@ -312,7 +317,7 @@ class SparseForwardDataFlowAnalysis
312317

313318
/// Get the lattice element for a value and create a dependency on the
314319
/// provided program point.
315-
const StateT *getLatticeElementFor(ProgramPoint point, Value value) {
320+
const StateT *getLatticeElementFor(ProgramPoint *point, Value value) {
316321
return static_cast<const StateT *>(
317322
AbstractSparseForwardDataFlowAnalysis::getLatticeElementFor(point,
318323
value));
@@ -377,10 +382,10 @@ class AbstractSparseBackwardDataFlowAnalysis : public DataFlowAnalysis {
377382
/// under it.
378383
LogicalResult initialize(Operation *top) override;
379384

380-
/// Visit a program point. If this is a call operation or an operation with
385+
/// Visit a program point. If it is after call operation or an operation with
381386
/// block or region control-flow, then operand lattices are set accordingly.
382387
/// Otherwise, invokes the operation transfer function (`visitOperationImpl`).
383-
LogicalResult visit(ProgramPoint point) override;
388+
LogicalResult visit(ProgramPoint *point) override;
384389

385390
protected:
386391
explicit AbstractSparseBackwardDataFlowAnalysis(
@@ -445,14 +450,14 @@ class AbstractSparseBackwardDataFlowAnalysis : public DataFlowAnalysis {
445450
/// Get the lattice element for a value, and also set up
446451
/// dependencies so that the analysis on the given ProgramPoint is re-invoked
447452
/// if the value changes.
448-
const AbstractSparseLattice *getLatticeElementFor(ProgramPoint point,
453+
const AbstractSparseLattice *getLatticeElementFor(ProgramPoint *point,
449454
Value value);
450455

451456
/// Get the lattice elements for a range of values, and also set up
452457
/// dependencies so that the analysis on the given ProgramPoint is re-invoked
453458
/// if any of the values change.
454459
SmallVector<const AbstractSparseLattice *>
455-
getLatticeElementsFor(ProgramPoint point, ValueRange values);
460+
getLatticeElementsFor(ProgramPoint *point, ValueRange values);
456461

457462
SymbolTableCollection &symbolTable;
458463
};
@@ -465,6 +470,10 @@ class AbstractSparseBackwardDataFlowAnalysis : public DataFlowAnalysis {
465470
/// backwards across the IR by implementing transfer functions for operations.
466471
///
467472
/// `StateT` is expected to be a subclass of `AbstractSparseLattice`.
473+
///
474+
/// Visit a program point in sparse backward data-flow analysis will invoke the
475+
/// transfer function of the operation preceding the program point iterator.
476+
/// Visit a program point at the begining of block will visit the block itself.
468477
template <typename StateT>
469478
class SparseBackwardDataFlowAnalysis
470479
: public AbstractSparseBackwardDataFlowAnalysis {

0 commit comments

Comments
 (0)