Skip to content

Commit cf602b9

Browse files
authored
[flang] handle fir.call in AliasAnalysis::getModRef (#117164)
fir.call side effects are hard to describe in a useful way using `MemoryEffectOpInterface` because it is impossible to list which memory location a user procedure read/write without doing a data flow analysis of its body (even PURE procedures may read from any module variable, Fortran SIMPLE procedure from F2023 will allow that, but they are far from common at that point). Fortran language specifications allow the compiler to deduce that a procedure call cannot access a variable in many cases This patch leverages this to extend `fir::AliasAnalysis::getModRef` to deal with fir.call. This will allow implementing "array = array_function()" optimization in a future patch.
1 parent 3414993 commit cf602b9

15 files changed

+641
-13
lines changed

flang/include/flang/Optimizer/Analysis/AliasAnalysis.h

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ struct AliasAnalysis {
129129
/// inlining happens an inlined fir.declare of the callee's
130130
/// dummy argument identifies the scope where the source
131131
/// may be treated as a dummy argument.
132-
mlir::Value instantiationPoint;
132+
mlir::Operation *instantiationPoint;
133133

134134
/// Whether the source was reached following data or box reference
135135
bool isData{false};
@@ -146,6 +146,8 @@ struct AliasAnalysis {
146146
/// Have we lost precision following the source such that
147147
/// even an exact match cannot be MustAlias?
148148
bool approximateSource;
149+
/// Source object is used in an internal procedure via host association.
150+
bool isCapturedInInternalProcedure{false};
149151

150152
/// Print information about the memory source to `os`.
151153
void print(llvm::raw_ostream &os) const;
@@ -157,6 +159,9 @@ struct AliasAnalysis {
157159
bool isData() const;
158160
bool isBoxData() const;
159161

162+
/// Is this source a variable from the Fortran source?
163+
bool isFortranUserVariable() const;
164+
160165
/// @name Dummy Argument Aliasing
161166
///
162167
/// Check conditions related to dummy argument aliasing.
@@ -194,11 +199,11 @@ struct AliasAnalysis {
194199
mlir::ModRefResult getModRef(mlir::Operation *op, mlir::Value location);
195200

196201
/// Return the memory source of a value.
197-
/// If getInstantiationPoint is true, the search for the source
202+
/// If getLastInstantiationPoint is true, the search for the source
198203
/// will stop at [hl]fir.declare if it represents a dummy
199204
/// argument declaration (i.e. it has the dummy_scope operand).
200205
fir::AliasAnalysis::Source getSource(mlir::Value,
201-
bool getInstantiationPoint = false);
206+
bool getLastInstantiationPoint = false);
202207

203208
private:
204209
/// Return true, if `ty` is a reference type to an object of derived type

flang/include/flang/Optimizer/Dialect/FortranVariableInterface.td

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,13 @@ def fir_FortranVariableOpInterface : OpInterface<"FortranVariableOpInterface"> {
184184
fir::FortranVariableFlagsEnum::target);
185185
}
186186

187+
/// Is this variable captured in an internal procedure via Fortran host association?
188+
bool isCapturedInInternalProcedure() {
189+
auto attrs = getFortranAttrs();
190+
return attrs && bitEnumContainsAny(*attrs,
191+
fir::FortranVariableFlagsEnum::internal_assoc);
192+
}
193+
187194
/// Is this variable a Fortran intent(in)?
188195
bool isIntentIn() {
189196
auto attrs = getFortranAttrs();

flang/lib/Lower/ConvertVariable.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1680,7 +1680,9 @@ isCapturedInInternalProcedure(Fortran::lower::AbstractConverter &converter,
16801680
if (funit->getHostAssoc().isAssociated(sym))
16811681
return true;
16821682
// Consider that any capture of a variable that is in an equivalence with the
1683+
// symbol imply that the storage of the symbol may also be accessed inside
16831684
// symbol implies that the storage of the symbol may also be accessed inside
1685+
16841686
// the internal procedure and flag it as captured.
16851687
if (const auto *equivSet = Fortran::semantics::FindEquivalenceSet(sym))
16861688
for (const Fortran::semantics::EquivalenceObject &eqObj : *equivSet)

flang/lib/Optimizer/Analysis/AliasAnalysis.cpp

Lines changed: 104 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include "flang/Optimizer/Dialect/FIRType.h"
1313
#include "flang/Optimizer/Dialect/FortranVariableInterface.h"
1414
#include "flang/Optimizer/HLFIR/HLFIROps.h"
15+
#include "flang/Optimizer/Support/InternalNames.h"
1516
#include "mlir/Analysis/AliasAnalysis.h"
1617
#include "mlir/Dialect/OpenMP/OpenMPDialect.h"
1718
#include "mlir/Dialect/OpenMP/OpenMPInterfaces.h"
@@ -96,6 +97,17 @@ bool AliasAnalysis::Source::isBoxData() const {
9697
origin.isData;
9798
}
9899

100+
bool AliasAnalysis::Source::isFortranUserVariable() const {
101+
if (!origin.instantiationPoint)
102+
return false;
103+
return llvm::TypeSwitch<mlir::Operation *, bool>(origin.instantiationPoint)
104+
.template Case<fir::DeclareOp, hlfir::DeclareOp>([&](auto declOp) {
105+
return fir::NameUniquer::deconstruct(declOp.getUniqName()).first ==
106+
fir::NameUniquer::NameKind::VARIABLE;
107+
})
108+
.Default([&](auto op) { return false; });
109+
}
110+
99111
bool AliasAnalysis::Source::mayBeDummyArgOrHostAssoc() const {
100112
return kind != SourceKind::Allocate && kind != SourceKind::Global;
101113
}
@@ -329,14 +341,92 @@ AliasResult AliasAnalysis::alias(Source lhsSrc, Source rhsSrc, mlir::Value lhs,
329341
// AliasAnalysis: getModRef
330342
//===----------------------------------------------------------------------===//
331343

344+
static bool isSavedLocal(const fir::AliasAnalysis::Source &src) {
345+
if (auto symRef = llvm::dyn_cast<mlir::SymbolRefAttr>(src.origin.u)) {
346+
auto [nameKind, deconstruct] =
347+
fir::NameUniquer::deconstruct(symRef.getLeafReference().getValue());
348+
return nameKind == fir::NameUniquer::NameKind::VARIABLE &&
349+
!deconstruct.procs.empty();
350+
}
351+
return false;
352+
}
353+
354+
static bool isCallToFortranUserProcedure(fir::CallOp call) {
355+
// TODO: indirect calls are excluded by these checks. Maybe some attribute is
356+
// needed to flag user calls in this case.
357+
if (fir::hasBindcAttr(call))
358+
return true;
359+
if (std::optional<mlir::SymbolRefAttr> callee = call.getCallee())
360+
return fir::NameUniquer::deconstruct(callee->getLeafReference().getValue())
361+
.first == fir::NameUniquer::NameKind::PROCEDURE;
362+
return false;
363+
}
364+
365+
static ModRefResult getCallModRef(fir::CallOp call, mlir::Value var) {
366+
// TODO: limit to Fortran functions??
367+
// 1. Detect variables that can be accessed indirectly.
368+
fir::AliasAnalysis aliasAnalysis;
369+
fir::AliasAnalysis::Source varSrc = aliasAnalysis.getSource(var);
370+
// If the variable is not a user variable, we cannot safely assume that
371+
// Fortran semantics apply (e.g., a bare alloca/allocmem result may very well
372+
// be placed in an allocatable/pointer descriptor and escape).
373+
374+
// All the logic below is based on Fortran semantics and only holds if this
375+
// is a call to a procedure from the Fortran source and this is a variable
376+
// from the Fortran source. Compiler generated temporaries or functions may
377+
// not adhere to this semantic.
378+
// TODO: add some opt-in or op-out mechanism for compiler generated temps.
379+
// An example of something currently problematic is the allocmem generated for
380+
// ALLOCATE of allocatable target. It currently does not have the target
381+
// attribute, which would lead this analysis to believe it cannot escape.
382+
if (!varSrc.isFortranUserVariable() || !isCallToFortranUserProcedure(call))
383+
return ModRefResult::getModAndRef();
384+
// Pointer and target may have been captured.
385+
if (varSrc.isTargetOrPointer())
386+
return ModRefResult::getModAndRef();
387+
// Host associated variables may be addressed indirectly via an internal
388+
// function call, whether the call is in the parent or an internal procedure.
389+
// Note that the host associated/internal procedure may be referenced
390+
// indirectly inside calls to non internal procedure. This is because internal
391+
// procedures may be captured or passed. As this is tricky to analyze, always
392+
// consider such variables may be accessed in any calls.
393+
if (varSrc.kind == fir::AliasAnalysis::SourceKind::HostAssoc ||
394+
varSrc.isCapturedInInternalProcedure)
395+
return ModRefResult::getModAndRef();
396+
// At that stage, it has been ruled out that local (including the saved ones)
397+
// and dummy cannot be indirectly accessed in the call.
398+
if (varSrc.kind != fir::AliasAnalysis::SourceKind::Allocate &&
399+
!varSrc.isDummyArgument()) {
400+
if (varSrc.kind != fir::AliasAnalysis::SourceKind::Global ||
401+
!isSavedLocal(varSrc))
402+
return ModRefResult::getModAndRef();
403+
}
404+
// 2. Check if the variable is passed via the arguments.
405+
for (auto arg : call.getArgs()) {
406+
if (fir::conformsWithPassByRef(arg.getType()) &&
407+
!aliasAnalysis.alias(arg, var).isNo()) {
408+
// TODO: intent(in) would allow returning Ref here. This can be obtained
409+
// in the func.func attributes for direct calls, but the module lookup is
410+
// linear with the number of MLIR symbols, which would introduce a pseudo
411+
// quadratic behavior num_calls * num_func.
412+
return ModRefResult::getModAndRef();
413+
}
414+
}
415+
// The call cannot access the variable.
416+
return ModRefResult::getNoModRef();
417+
}
418+
332419
/// This is mostly inspired by MLIR::LocalAliasAnalysis with 2 notable
333420
/// differences 1) Regions are not handled here but will be handled by a data
334421
/// flow analysis to come 2) Allocate and Free effects are considered
335422
/// modifying
336423
ModRefResult AliasAnalysis::getModRef(Operation *op, Value location) {
337424
MemoryEffectOpInterface interface = dyn_cast<MemoryEffectOpInterface>(op);
338-
if (!interface)
425+
if (!interface) {
426+
if (auto call = llvm::dyn_cast<fir::CallOp>(op))
427+
return getCallModRef(call, location);
339428
return ModRefResult::getModAndRef();
429+
}
340430

341431
// Build a ModRefResult by merging the behavior of the effects of this
342432
// operation.
@@ -408,19 +498,20 @@ static Value getPrivateArg(omp::BlockArgOpenMPOpInterface &argIface,
408498
}
409499

410500
AliasAnalysis::Source AliasAnalysis::getSource(mlir::Value v,
411-
bool getInstantiationPoint) {
501+
bool getLastInstantiationPoint) {
412502
auto *defOp = v.getDefiningOp();
413503
SourceKind type{SourceKind::Unknown};
414504
mlir::Type ty;
415505
bool breakFromLoop{false};
416506
bool approximateSource{false};
507+
bool isCapturedInInternalProcedure{false};
417508
bool followBoxData{mlir::isa<fir::BaseBoxType>(v.getType())};
418509
bool isBoxRef{fir::isa_ref_type(v.getType()) &&
419510
mlir::isa<fir::BaseBoxType>(fir::unwrapRefType(v.getType()))};
420511
bool followingData = !isBoxRef;
421512
mlir::SymbolRefAttr global;
422513
Source::Attributes attributes;
423-
mlir::Value instantiationPoint;
514+
mlir::Operation *instantiationPoint{nullptr};
424515
while (defOp && !breakFromLoop) {
425516
ty = defOp->getResultTypes()[0];
426517
llvm::TypeSwitch<Operation *>(defOp)
@@ -548,6 +639,8 @@ AliasAnalysis::Source AliasAnalysis::getSource(mlir::Value v,
548639
// is the only carrier of the variable attributes,
549640
// so we have to collect them here.
550641
attributes |= getAttrsFromVariable(varIf);
642+
isCapturedInInternalProcedure |=
643+
varIf.isCapturedInInternalProcedure();
551644
if (varIf.isHostAssoc()) {
552645
// Do not track past such DeclareOp, because it does not
553646
// currently provide any useful information. The host associated
@@ -561,10 +654,10 @@ AliasAnalysis::Source AliasAnalysis::getSource(mlir::Value v,
561654
breakFromLoop = true;
562655
return;
563656
}
564-
if (getInstantiationPoint) {
657+
if (getLastInstantiationPoint) {
565658
// Fetch only the innermost instantiation point.
566659
if (!instantiationPoint)
567-
instantiationPoint = op->getResult(0);
660+
instantiationPoint = op;
568661

569662
if (op.getDummyScope()) {
570663
// Do not track past DeclareOp that has the dummy_scope
@@ -575,6 +668,8 @@ AliasAnalysis::Source AliasAnalysis::getSource(mlir::Value v,
575668
breakFromLoop = true;
576669
return;
577670
}
671+
} else {
672+
instantiationPoint = op;
578673
}
579674
// TODO: Look for the fortran attributes present on the operation
580675
// Track further through the operand
@@ -620,13 +715,15 @@ AliasAnalysis::Source AliasAnalysis::getSource(mlir::Value v,
620715
type,
621716
ty,
622717
attributes,
623-
approximateSource};
718+
approximateSource,
719+
isCapturedInInternalProcedure};
624720
}
625721
return {{v, instantiationPoint, followingData},
626722
type,
627723
ty,
628724
attributes,
629-
approximateSource};
725+
approximateSource,
726+
isCapturedInInternalProcedure};
630727
}
631728

632729
} // namespace fir

flang/lib/Optimizer/Analysis/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ add_flang_library(FIRAnalysis
44

55
DEPENDS
66
FIRDialect
7+
FIRSupport
78
HLFIRDialect
89
MLIRIR
910
MLIROpenMPDialect

flang/lib/Optimizer/Transforms/AddAliasTags.cpp

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -209,12 +209,11 @@ void AddAliasTagsPass::runOnAliasInterface(fir::FirAliasTagOpInterface op,
209209
state.processFunctionScopes(func);
210210

211211
fir::DummyScopeOp scopeOp;
212-
if (auto declVal = source.origin.instantiationPoint) {
212+
if (auto declOp = source.origin.instantiationPoint) {
213213
// If the source is a dummy argument within some fir.dummy_scope,
214214
// then find the corresponding innermost scope to be used for finding
215215
// the right TBAA tree.
216-
auto declareOp =
217-
mlir::dyn_cast_or_null<fir::DeclareOp>(declVal.getDefiningOp());
216+
auto declareOp = mlir::dyn_cast<fir::DeclareOp>(declOp);
218217
assert(declareOp && "Instantiation point must be fir.declare");
219218
if (auto dummyScope = declareOp.getDummyScope())
220219
scopeOp = mlir::cast<fir::DummyScopeOp>(dummyScope.getDefiningOp());
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
#!/usr/bin/env python3
2+
3+
"""
4+
Add attributes hook in an HLFIR code to test fir.call ModRef effects
5+
with the test-fir-alias-analysis-modref pass.
6+
7+
This will insert mod ref test hook:
8+
- to any fir.call to a function which name starts with "test_effect_"
9+
- to any hlfir.declare for variable which name starts with "test_var_"
10+
"""
11+
12+
import sys
13+
import re
14+
15+
for line in sys.stdin:
16+
line = re.sub(
17+
r"(fir.call @_\w*P)(test_effect_\w*)(\(.*) : ",
18+
r'\1\2\3 {test.ptr ="\2"} : ',
19+
line,
20+
)
21+
line = re.sub(
22+
r'(hlfir.declare .*uniq_name =.*E)(test_var_\w*)"',
23+
r'\1\2", test.ptr ="\2"',
24+
line,
25+
)
26+
sys.stdout.write(line)
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// RUN: fir-opt -pass-pipeline='builtin.module(func.func(test-fir-alias-analysis-modref))' \
2+
// RUN: --mlir-disable-threading %s -o /dev/null 2>&1 | FileCheck %s
3+
4+
// Test fir.call modref with internal procedures after the host function has been inlined in
5+
// some other function. This checks that the last hlfir.declare "internal_assoc" flags that
6+
// marks a variable that was captured is still considered even though there is no such flags
7+
// on the declare at the top of the chain.
8+
//
9+
// In other words, in the following Fortran example, "x" should be considered
10+
// modified by "call internal_proc" after "call inline_me" was inlined into
11+
// "test".
12+
//
13+
// subroutine test()
14+
// real :: x(10)
15+
// call inline_me(x)
16+
// end subroutine
17+
//
18+
// subroutine inline_me(x)
19+
// real :: x(10)
20+
// call internal_proc()
21+
// contains
22+
// subroutine internal_proc()
23+
// call some_external(x)
24+
// end subroutine
25+
// end subroutine
26+
27+
func.func @_QPtest() {
28+
%c0_i32 = arith.constant 0 : i32
29+
%c10 = arith.constant 10 : index
30+
%0 = fir.alloca !fir.array<10xf32> {bindc_name = "x", uniq_name = "_QFtestEx"}
31+
%1 = fir.shape %c10 : (index) -> !fir.shape<1>
32+
%2:2 = hlfir.declare %0(%1) {uniq_name = "_QFtestEx"} : (!fir.ref<!fir.array<10xf32>>, !fir.shape<1>) -> (!fir.ref<!fir.array<10xf32>>, !fir.ref<!fir.array<10xf32>>)
33+
%3 = fir.dummy_scope : !fir.dscope
34+
%4:2 = hlfir.declare %2#1(%1) dummy_scope %3 {test.ptr = "x", fortran_attrs = #fir.var_attrs<internal_assoc>, uniq_name = "_QFinline_meEx"} : (!fir.ref<!fir.array<10xf32>>, !fir.shape<1>, !fir.dscope) -> (!fir.ref<!fir.array<10xf32>>, !fir.ref<!fir.array<10xf32>>)
35+
%5 = fir.alloca tuple<!fir.box<!fir.array<10xf32>>>
36+
%6 = fir.coordinate_of %5, %c0_i32 : (!fir.ref<tuple<!fir.box<!fir.array<10xf32>>>>, i32) -> !fir.ref<!fir.box<!fir.array<10xf32>>>
37+
%7 = fir.embox %4#1(%1) : (!fir.ref<!fir.array<10xf32>>, !fir.shape<1>) -> !fir.box<!fir.array<10xf32>>
38+
fir.store %7 to %6 : !fir.ref<!fir.box<!fir.array<10xf32>>>
39+
fir.call @_QFinline_mePinternal_proc(%5) {test.ptr="internal_proc"} : (!fir.ref<tuple<!fir.box<!fir.array<10xf32>>>>) -> ()
40+
return
41+
}
42+
func.func private @_QFinline_mePinternal_proc(!fir.ref<tuple<!fir.box<!fir.array<10xf32>>>> {fir.host_assoc}) attributes {fir.host_symbol = @_QPinline_me}
43+
44+
// CHECK-LABEL: Testing : "_QPtest"
45+
// CHECK: internal_proc -> x#0: ModRef

0 commit comments

Comments
 (0)