Skip to content

Commit a83bb35

Browse files
authored
[flang][fir] Add fir.local op for locality specifiers (#138505)
Adds a new `fir.local` op to model `local` and `local_init` locality specifiers. This op is a clone of `omp.private`. In particular, this new op also models the privatization/localization logic of an SSA value in the `fir` dialect just like `omp.private` does for OpenMP. PR stack: - #137928 - #138505 (this PR) - #138506 - #138512 - #138534 - #138816
1 parent 7157228 commit a83bb35

File tree

5 files changed

+344
-2
lines changed

5 files changed

+344
-2
lines changed

flang/include/flang/Optimizer/Dialect/FIRAttr.td

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,4 +200,23 @@ def fir_OpenMPSafeTempArrayCopyAttr : fir_Attr<"OpenMPSafeTempArrayCopy"> {
200200
}];
201201
}
202202

203+
def LocalitySpecTypeLocal : I32EnumAttrCase<"Local", 0, "local">;
204+
def LocalitySpecTypeLocalInit
205+
: I32EnumAttrCase<"LocalInit", 1, "local_init">;
206+
207+
def LocalitySpecifierType : I32EnumAttr<
208+
"LocalitySpecifierType",
209+
"Type of a locality specifier", [
210+
LocalitySpecTypeLocal,
211+
LocalitySpecTypeLocalInit
212+
]> {
213+
let genSpecializedAttr = 0;
214+
let cppNamespace = "::fir";
215+
}
216+
217+
def LocalitySpecifierTypeAttr : EnumAttr<FIROpsDialect, LocalitySpecifierType,
218+
"locality_specifier_type"> {
219+
let assemblyFormat = "`{` `type` `=` $value `}`";
220+
}
221+
203222
#endif // FIR_DIALECT_FIR_ATTRS

flang/include/flang/Optimizer/Dialect/FIROps.td

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3485,6 +3485,137 @@ def fir_BoxTotalElementsOp
34853485
let hasCanonicalizer = 1;
34863486
}
34873487

3488+
def YieldOp : fir_Op<"yield",
3489+
[Pure, ReturnLike, Terminator,
3490+
ParentOneOf<["LocalitySpecifierOp"]>]> {
3491+
let summary = "loop yield and termination operation";
3492+
let description = [{
3493+
"fir.yield" yields SSA values from a fir dialect op region and
3494+
terminates the region. The semantics of how the values are yielded is
3495+
defined by the parent operation.
3496+
}];
3497+
3498+
let arguments = (ins Variadic<AnyType>:$results);
3499+
3500+
let builders = [
3501+
OpBuilder<(ins), [{ build($_builder, $_state, {}); }]>
3502+
];
3503+
3504+
let assemblyFormat = "( `(` $results^ `:` type($results) `)` )? attr-dict";
3505+
}
3506+
3507+
def fir_LocalitySpecifierOp : fir_Op<"local", [IsolatedFromAbove]> {
3508+
let summary = "Provides declaration of local and local_init logic.";
3509+
let description = [{
3510+
This operation provides a declaration of how to implement the
3511+
localization of a variable. The dialect users should provide
3512+
which type should be allocated for this variable. The allocated (usually by
3513+
alloca) variable is passed to the initialization region which does everything
3514+
else (e.g. initialization of Fortran runtime descriptors). Information about
3515+
how to initialize the copy from the original item should be given in the
3516+
copy region, and if needed, how to deallocate memory (allocated by the
3517+
initialization region) in the dealloc region.
3518+
3519+
Examples:
3520+
3521+
* `local(x)` would not need any regions because no initialization is
3522+
required by the standard for i32 variables and this is not local_init.
3523+
```
3524+
fir.local {type = local} @x.localizer : i32
3525+
```
3526+
3527+
* `local_init(x)` would be emitted as:
3528+
```
3529+
fir.local {type = local_init} @x.localizer : i32 copy {
3530+
^bb0(%arg0: !fir.ref<i32>, %arg1: !fir.ref<i32>):
3531+
// %arg0 is the original host variable.
3532+
// %arg1 represents the memory allocated for this private variable.
3533+
... copy from host to the localized clone ....
3534+
fir.yield(%arg1 : !fir.ref<i32>)
3535+
}
3536+
```
3537+
3538+
* `local(x)` for "allocatables" would be emitted as:
3539+
```
3540+
fir.local {type = local} @x.localizer : !some.type init {
3541+
^bb0(%arg0: !fir.ref<!some.type>, %arg1: !fir.ref<!some.type>):
3542+
// initialize %arg1, using %arg0 as a mold for allocations.
3543+
// For example if %arg0 is a heap allocated array with a runtime determined
3544+
// length and !some.type is a runtime type descriptor, the init region
3545+
// will read the array length from %arg0, and heap allocate an array of the
3546+
// right length and initialize %arg1 to contain the array allocation and
3547+
// length.
3548+
fir.yield(%arg1 : !fir.ref<!some.type>)
3549+
} dealloc {
3550+
^bb0(%arg0: !fir.ref<!some.type>):
3551+
// ... deallocate memory allocated by the init region...
3552+
// In the example above, this will free the heap allocated array data.
3553+
fir.yield
3554+
}
3555+
```
3556+
3557+
There are no restrictions on the body except for:
3558+
- The `dealloc` regions has a single argument.
3559+
- The `init` & `copy` regions have 2 arguments.
3560+
- All three regions are terminated by `fir.yield` ops.
3561+
The above restrictions and other obvious restrictions (e.g. verifying the
3562+
type of yielded values) are verified by the custom op verifier. The actual
3563+
contents of the blocks inside all regions are not verified.
3564+
3565+
Instances of this op would then be used by ops that model directives that
3566+
accept data-sharing attribute clauses.
3567+
3568+
The `sym_name` attribute provides a symbol by which the privatizer op can be
3569+
referenced by other dialect ops.
3570+
3571+
The `type` attribute is the type of the value being localized. This type
3572+
will be implicitly allocated in MLIR->LLVMIR conversion and passed as the
3573+
second argument to the init region. Therefore the type of arguments to
3574+
the regions should be a type which represents a pointer to `type`.
3575+
3576+
The `locality_specifier_type` attribute specifies whether the localized
3577+
corresponds to a `local` or a `local_init` specifier.
3578+
}];
3579+
3580+
let arguments = (ins SymbolNameAttr:$sym_name,
3581+
TypeAttrOf<AnyType>:$type,
3582+
LocalitySpecifierTypeAttr:$locality_specifier_type);
3583+
3584+
let regions = (region AnyRegion:$init_region,
3585+
AnyRegion:$copy_region,
3586+
AnyRegion:$dealloc_region);
3587+
3588+
let assemblyFormat = [{
3589+
$locality_specifier_type $sym_name `:` $type
3590+
(`init` $init_region^)?
3591+
(`copy` $copy_region^)?
3592+
(`dealloc` $dealloc_region^)?
3593+
attr-dict
3594+
}];
3595+
3596+
let builders = [
3597+
OpBuilder<(ins CArg<"mlir::TypeRange">:$result,
3598+
CArg<"mlir::StringAttr">:$sym_name,
3599+
CArg<"mlir::TypeAttr">:$type)>
3600+
];
3601+
3602+
let extraClassDeclaration = [{
3603+
/// Get the type for arguments to nested regions. This should
3604+
/// generally be either the same as getType() or some pointer
3605+
/// type (pointing to the type allocated by this op).
3606+
/// This method will return Type{nullptr} if there are no nested
3607+
/// regions.
3608+
mlir::Type getArgType() {
3609+
for (mlir::Region *region : getRegions())
3610+
for (mlir::Type ty : region->getArgumentTypes())
3611+
return ty;
3612+
return nullptr;
3613+
}
3614+
}];
3615+
3616+
let hasRegionVerifier = 1;
3617+
}
3618+
34883619
def fir_DoConcurrentOp : fir_Op<"do_concurrent",
34893620
[SingleBlock, AutomaticAllocationScope]> {
34903621
let summary = "do concurrent loop wrapper";

flang/lib/Optimizer/Dialect/FIROps.cpp

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4909,6 +4909,105 @@ void fir::BoxTotalElementsOp::getCanonicalizationPatterns(
49094909
patterns.add<SimplifyBoxTotalElementsOp>(context);
49104910
}
49114911

4912+
//===----------------------------------------------------------------------===//
4913+
// LocalitySpecifierOp
4914+
//===----------------------------------------------------------------------===//
4915+
4916+
llvm::LogicalResult fir::LocalitySpecifierOp::verifyRegions() {
4917+
mlir::Type argType = getArgType();
4918+
auto verifyTerminator = [&](mlir::Operation *terminator,
4919+
bool yieldsValue) -> llvm::LogicalResult {
4920+
if (!terminator->getBlock()->getSuccessors().empty())
4921+
return llvm::success();
4922+
4923+
if (!llvm::isa<fir::YieldOp>(terminator))
4924+
return mlir::emitError(terminator->getLoc())
4925+
<< "expected exit block terminator to be an `fir.yield` op.";
4926+
4927+
YieldOp yieldOp = llvm::cast<YieldOp>(terminator);
4928+
mlir::TypeRange yieldedTypes = yieldOp.getResults().getTypes();
4929+
4930+
if (!yieldsValue) {
4931+
if (yieldedTypes.empty())
4932+
return llvm::success();
4933+
4934+
return mlir::emitError(terminator->getLoc())
4935+
<< "Did not expect any values to be yielded.";
4936+
}
4937+
4938+
if (yieldedTypes.size() == 1 && yieldedTypes.front() == argType)
4939+
return llvm::success();
4940+
4941+
auto error = mlir::emitError(yieldOp.getLoc())
4942+
<< "Invalid yielded value. Expected type: " << argType
4943+
<< ", got: ";
4944+
4945+
if (yieldedTypes.empty())
4946+
error << "None";
4947+
else
4948+
error << yieldedTypes;
4949+
4950+
return error;
4951+
};
4952+
4953+
auto verifyRegion = [&](mlir::Region &region, unsigned expectedNumArgs,
4954+
llvm::StringRef regionName,
4955+
bool yieldsValue) -> llvm::LogicalResult {
4956+
assert(!region.empty());
4957+
4958+
if (region.getNumArguments() != expectedNumArgs)
4959+
return mlir::emitError(region.getLoc())
4960+
<< "`" << regionName << "`: "
4961+
<< "expected " << expectedNumArgs
4962+
<< " region arguments, got: " << region.getNumArguments();
4963+
4964+
for (mlir::Block &block : region) {
4965+
// MLIR will verify the absence of the terminator for us.
4966+
if (!block.mightHaveTerminator())
4967+
continue;
4968+
4969+
if (failed(verifyTerminator(block.getTerminator(), yieldsValue)))
4970+
return llvm::failure();
4971+
}
4972+
4973+
return llvm::success();
4974+
};
4975+
4976+
// Ensure all of the region arguments have the same type
4977+
for (mlir::Region *region : getRegions())
4978+
for (mlir::Type ty : region->getArgumentTypes())
4979+
if (ty != argType)
4980+
return emitError() << "Region argument type mismatch: got " << ty
4981+
<< " expected " << argType << ".";
4982+
4983+
mlir::Region &initRegion = getInitRegion();
4984+
if (!initRegion.empty() &&
4985+
failed(verifyRegion(getInitRegion(), /*expectedNumArgs=*/2, "init",
4986+
/*yieldsValue=*/true)))
4987+
return llvm::failure();
4988+
4989+
LocalitySpecifierType dsType = getLocalitySpecifierType();
4990+
4991+
if (dsType == LocalitySpecifierType::Local && !getCopyRegion().empty())
4992+
return emitError("`local` specifiers do not require a `copy` region.");
4993+
4994+
if (dsType == LocalitySpecifierType::LocalInit && getCopyRegion().empty())
4995+
return emitError(
4996+
"`local_init` specifiers require at least a `copy` region.");
4997+
4998+
if (dsType == LocalitySpecifierType::LocalInit &&
4999+
failed(verifyRegion(getCopyRegion(), /*expectedNumArgs=*/2, "copy",
5000+
/*yieldsValue=*/true)))
5001+
return llvm::failure();
5002+
5003+
if (!getDeallocRegion().empty() &&
5004+
failed(verifyRegion(getDeallocRegion(), /*expectedNumArgs=*/1, "dealloc",
5005+
/*yieldsValue=*/false)))
5006+
return llvm::failure();
5007+
5008+
return llvm::success();
5009+
}
5010+
49125011
//===----------------------------------------------------------------------===//
49135012
// DoConcurrentOp
49145013
//===----------------------------------------------------------------------===//

flang/test/Fir/do_concurrent.fir

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,3 +90,22 @@ func.func @dc_2d_reduction(%i_lb: index, %i_ub: index, %i_st: index,
9090
// CHECK: fir.store %[[J_IV_CVT]] to %[[J]] : !fir.ref<i32>
9191
// CHECK: }
9292
// CHECK: }
93+
94+
95+
fir.local {type = local} @local_privatizer : i32
96+
97+
// CHECK: fir.local {type = local} @[[LOCAL_PRIV_SYM:local_privatizer]] : i32
98+
99+
fir.local {type = local_init} @local_init_privatizer : i32 copy {
100+
^bb0(%arg0: !fir.ref<i32>, %arg1: !fir.ref<i32>):
101+
%0 = fir.load %arg0 : !fir.ref<i32>
102+
fir.store %0 to %arg1 : !fir.ref<i32>
103+
fir.yield(%arg1 : !fir.ref<i32>)
104+
}
105+
106+
// CHECK: fir.local {type = local_init} @[[LOCAL_INIT_PRIV_SYM:local_init_privatizer]] : i32
107+
// CHECK: ^bb0(%[[ORIG_VAL:.*]]: !fir.ref<i32>, %[[LOCAL_VAL:.*]]: !fir.ref<i32>):
108+
// CHECK: %[[ORIG_VAL_LD:.*]] = fir.load %[[ORIG_VAL]]
109+
// CHECK: fir.store %[[ORIG_VAL_LD]] to %[[LOCAL_VAL]] : !fir.ref<i32>
110+
// CHECK: fir.yield(%[[LOCAL_VAL]] : !fir.ref<i32>)
111+
// CHECK: }

flang/test/Fir/invalid.fir

Lines changed: 76 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
2-
31
// RUN: fir-opt -split-input-file -verify-diagnostics --strict-fir-volatile-verifier %s
42

53
// expected-error@+1{{custom op 'fir.string_lit' must have character type}}
@@ -1311,3 +1309,79 @@ func.func @bad_convert_volatile6(%arg0: !fir.ref<i32>) -> !fir.ref<i64> {
13111309
%0 = fir.volatile_cast %arg0 : (!fir.ref<i32>) -> !fir.ref<i64>
13121310
return %0 : !fir.ref<i64>
13131311
}
1312+
1313+
// -----
1314+
1315+
fir.local {type = local} @x.localizer : i32 init {
1316+
^bb0(%arg0: i32, %arg1: i32):
1317+
%0 = arith.constant 0.0 : f32
1318+
// expected-error @below {{Invalid yielded value. Expected type: 'i32', got: 'f32'}}
1319+
fir.yield(%0 : f32)
1320+
}
1321+
1322+
// -----
1323+
1324+
// expected-error @below {{Region argument type mismatch: got 'f32' expected 'i32'.}}
1325+
fir.local {type = local} @x.localizer : i32 init {
1326+
^bb0(%arg0: i32, %arg1: f32):
1327+
fir.yield
1328+
}
1329+
1330+
// -----
1331+
1332+
fir.local {type = local} @x.localizer : f32 init {
1333+
^bb0(%arg0: f32, %arg1: f32):
1334+
fir.yield(%arg0: f32)
1335+
} dealloc {
1336+
^bb0(%arg0: f32):
1337+
// expected-error @below {{Did not expect any values to be yielded.}}
1338+
fir.yield(%arg0 : f32)
1339+
}
1340+
1341+
// -----
1342+
1343+
fir.local {type = local} @x.localizer : i32 init {
1344+
^bb0(%arg0: i32, %arg1: i32):
1345+
// expected-error @below {{expected exit block terminator to be an `fir.yield` op.}}
1346+
fir.unreachable
1347+
}
1348+
1349+
// -----
1350+
1351+
// expected-error @below {{`init`: expected 2 region arguments, got: 1}}
1352+
fir.local {type = local} @x.localizer : f32 init {
1353+
^bb0(%arg0: f32):
1354+
fir.yield(%arg0 : f32)
1355+
}
1356+
1357+
// -----
1358+
1359+
// expected-error @below {{`copy`: expected 2 region arguments, got: 1}}
1360+
fir.local {type = local_init} @x.privatizer : f32 copy {
1361+
^bb0(%arg0: f32):
1362+
fir.yield(%arg0 : f32)
1363+
}
1364+
1365+
// -----
1366+
1367+
// expected-error @below {{`dealloc`: expected 1 region arguments, got: 2}}
1368+
fir.local {type = local} @x.localizer : f32 dealloc {
1369+
^bb0(%arg0: f32, %arg1: f32):
1370+
fir.yield
1371+
}
1372+
1373+
// -----
1374+
1375+
// expected-error @below {{`local` specifiers do not require a `copy` region.}}
1376+
fir.local {type = local} @x.localizer : f32 copy {
1377+
^bb0(%arg0: f32, %arg1 : f32):
1378+
fir.yield(%arg0 : f32)
1379+
}
1380+
1381+
// -----
1382+
1383+
// expected-error @below {{`local_init` specifiers require at least a `copy` region.}}
1384+
fir.local {type = local_init} @x.localizer : f32 init {
1385+
^bb0(%arg0: f32, %arg1: f32):
1386+
fir.yield(%arg0 : f32)
1387+
}

0 commit comments

Comments
 (0)