Skip to content

Commit 84c95d6

Browse files
committed
[flang] handle fir.call in getModRef
1 parent c12869e commit 84c95d6

File tree

14 files changed

+614
-13
lines changed

14 files changed

+614
-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/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 bellows are based on Fortran semantics and only holds if this
375+
// is a call to a procedure form 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: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
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(r'(fir.call @_\w*P)(test_effect_\w*)(\(.*) : ', r'\1\2\3 {test.ptr ="\2"} : ', line)
17+
line = re.sub(r'(hlfir.declare .*uniq_name =.*E)(test_var_\w*)"', r'\1\2", test.ptr ="\2"', line)
18+
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
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
! RUN: bbc -emit-hlfir %s -o - | %python %S/gen_mod_ref_test.py | \
2+
! RUN: fir-opt -pass-pipeline='builtin.module(func.func(test-fir-alias-analysis-modref))' \
3+
! RUN: --mlir-disable-threading -o /dev/null 2>&1 | FileCheck %s
4+
5+
! Test fir.call modref when arguments are passed to the call. This focus
6+
! on the possibility of "direct" effects (taken via the arguments, and not
7+
! via some indirect access via global states).
8+
9+
subroutine test_simple()
10+
implicit none
11+
real :: test_var_x, test_var_y
12+
call test_effect_external(test_var_x)
13+
end subroutine
14+
! CHECK-LABEL: Testing : "_QPtest_simple"
15+
! CHECK: test_effect_external -> test_var_x#0: ModRef
16+
! CHECK: test_effect_external -> test_var_y#0: NoModRef
17+
18+
subroutine test_equivalence()
19+
implicit none
20+
real :: test_var_x, test_var_y
21+
equivalence(test_var_x, test_var_y)
22+
call test_effect_external(test_var_x)
23+
end subroutine
24+
! CHECK-LABEL: Testing : "_QPtest_equivalence"
25+
! CHECK: test_effect_external -> test_var_x#0: ModRef
26+
! CHECK: test_effect_external -> test_var_y#0: ModRef
27+
28+
subroutine test_pointer()
29+
implicit none
30+
real, target :: test_var_x, test_var_y
31+
real, pointer :: p
32+
p => test_var_x
33+
call test_effect_external(p)
34+
end subroutine
35+
! CHECK-LABEL: Testing : "_QPtest_pointer"
36+
! CHECK: test_effect_external -> test_var_x#0: ModRef
37+
! TODO: test_var_y should be NoModRef, the alias analysis is currently very
38+
! conservative whenever pointer/allocatable descriptors are involved (mostly
39+
! because it needs to make sure it is dealing descriptors for POINTER/ALLOCATABLE
40+
! from the Fortran source and that it can apply language rules).
41+
! CHECK: test_effect_external -> test_var_y#0: ModRef
42+
43+
subroutine test_array_1(test_var_x)
44+
implicit none
45+
real :: test_var_x(:), test_var_y
46+
call test_effect_external(test_var_x(10))
47+
end subroutine
48+
! CHECK-LABEL: Testing : "_QPtest_array_1"
49+
! CHECK: test_effect_external -> test_var_x#0: ModRef
50+
! CHECK: test_effect_external -> test_var_y#0: NoModRef
51+
52+
subroutine test_array_copy_in(test_var_x)
53+
implicit none
54+
real :: test_var_x(:), test_var_y
55+
call test_effect_external_2(test_var_x)
56+
end subroutine
57+
! CHECK-LABEL: Testing : "_QPtest_array_copy_in"
58+
! CHECK: test_effect_external_2 -> test_var_x#0: ModRef
59+
! TODO: copy-in/out is currently badly understood by alias analysis, this
60+
! causes the modref analysis to think the argument may alias with anyting.
61+
! test_var_y should obviously be considered NoMoRef in the call.
62+
! CHECK: test_effect_external_2 -> test_var_y#0: ModRef

0 commit comments

Comments
 (0)