[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: - https://github.com/llvm/llvm-project/pull/137928 - https://github.com/llvm/llvm-project/pull/138505 (this PR) - https://github.com/llvm/llvm-project/pull/138506 - https://github.com/llvm/llvm-project/pull/138512 - https://github.com/llvm/llvm-project/pull/138534 - https://github.com/llvm/llvm-project/pull/138816
This commit is contained in:
parent
7157228667
commit
a83bb35e99
@ -200,4 +200,23 @@ def fir_OpenMPSafeTempArrayCopyAttr : fir_Attr<"OpenMPSafeTempArrayCopy"> {
|
||||
}];
|
||||
}
|
||||
|
||||
def LocalitySpecTypeLocal : I32EnumAttrCase<"Local", 0, "local">;
|
||||
def LocalitySpecTypeLocalInit
|
||||
: I32EnumAttrCase<"LocalInit", 1, "local_init">;
|
||||
|
||||
def LocalitySpecifierType : I32EnumAttr<
|
||||
"LocalitySpecifierType",
|
||||
"Type of a locality specifier", [
|
||||
LocalitySpecTypeLocal,
|
||||
LocalitySpecTypeLocalInit
|
||||
]> {
|
||||
let genSpecializedAttr = 0;
|
||||
let cppNamespace = "::fir";
|
||||
}
|
||||
|
||||
def LocalitySpecifierTypeAttr : EnumAttr<FIROpsDialect, LocalitySpecifierType,
|
||||
"locality_specifier_type"> {
|
||||
let assemblyFormat = "`{` `type` `=` $value `}`";
|
||||
}
|
||||
|
||||
#endif // FIR_DIALECT_FIR_ATTRS
|
||||
|
@ -3485,6 +3485,137 @@ def fir_BoxTotalElementsOp
|
||||
let hasCanonicalizer = 1;
|
||||
}
|
||||
|
||||
def YieldOp : fir_Op<"yield",
|
||||
[Pure, ReturnLike, Terminator,
|
||||
ParentOneOf<["LocalitySpecifierOp"]>]> {
|
||||
let summary = "loop yield and termination operation";
|
||||
let description = [{
|
||||
"fir.yield" yields SSA values from a fir dialect op region and
|
||||
terminates the region. The semantics of how the values are yielded is
|
||||
defined by the parent operation.
|
||||
}];
|
||||
|
||||
let arguments = (ins Variadic<AnyType>:$results);
|
||||
|
||||
let builders = [
|
||||
OpBuilder<(ins), [{ build($_builder, $_state, {}); }]>
|
||||
];
|
||||
|
||||
let assemblyFormat = "( `(` $results^ `:` type($results) `)` )? attr-dict";
|
||||
}
|
||||
|
||||
def fir_LocalitySpecifierOp : fir_Op<"local", [IsolatedFromAbove]> {
|
||||
let summary = "Provides declaration of local and local_init logic.";
|
||||
let description = [{
|
||||
This operation provides a declaration of how to implement the
|
||||
localization of a variable. The dialect users should provide
|
||||
which type should be allocated for this variable. The allocated (usually by
|
||||
alloca) variable is passed to the initialization region which does everything
|
||||
else (e.g. initialization of Fortran runtime descriptors). Information about
|
||||
how to initialize the copy from the original item should be given in the
|
||||
copy region, and if needed, how to deallocate memory (allocated by the
|
||||
initialization region) in the dealloc region.
|
||||
|
||||
Examples:
|
||||
|
||||
* `local(x)` would not need any regions because no initialization is
|
||||
required by the standard for i32 variables and this is not local_init.
|
||||
```
|
||||
fir.local {type = local} @x.localizer : i32
|
||||
```
|
||||
|
||||
* `local_init(x)` would be emitted as:
|
||||
```
|
||||
fir.local {type = local_init} @x.localizer : i32 copy {
|
||||
^bb0(%arg0: !fir.ref<i32>, %arg1: !fir.ref<i32>):
|
||||
// %arg0 is the original host variable.
|
||||
// %arg1 represents the memory allocated for this private variable.
|
||||
... copy from host to the localized clone ....
|
||||
fir.yield(%arg1 : !fir.ref<i32>)
|
||||
}
|
||||
```
|
||||
|
||||
* `local(x)` for "allocatables" would be emitted as:
|
||||
```
|
||||
fir.local {type = local} @x.localizer : !some.type init {
|
||||
^bb0(%arg0: !fir.ref<!some.type>, %arg1: !fir.ref<!some.type>):
|
||||
// initialize %arg1, using %arg0 as a mold for allocations.
|
||||
// For example if %arg0 is a heap allocated array with a runtime determined
|
||||
// length and !some.type is a runtime type descriptor, the init region
|
||||
// will read the array length from %arg0, and heap allocate an array of the
|
||||
// right length and initialize %arg1 to contain the array allocation and
|
||||
// length.
|
||||
fir.yield(%arg1 : !fir.ref<!some.type>)
|
||||
} dealloc {
|
||||
^bb0(%arg0: !fir.ref<!some.type>):
|
||||
// ... deallocate memory allocated by the init region...
|
||||
// In the example above, this will free the heap allocated array data.
|
||||
fir.yield
|
||||
}
|
||||
```
|
||||
|
||||
There are no restrictions on the body except for:
|
||||
- The `dealloc` regions has a single argument.
|
||||
- The `init` & `copy` regions have 2 arguments.
|
||||
- All three regions are terminated by `fir.yield` ops.
|
||||
The above restrictions and other obvious restrictions (e.g. verifying the
|
||||
type of yielded values) are verified by the custom op verifier. The actual
|
||||
contents of the blocks inside all regions are not verified.
|
||||
|
||||
Instances of this op would then be used by ops that model directives that
|
||||
accept data-sharing attribute clauses.
|
||||
|
||||
The `sym_name` attribute provides a symbol by which the privatizer op can be
|
||||
referenced by other dialect ops.
|
||||
|
||||
The `type` attribute is the type of the value being localized. This type
|
||||
will be implicitly allocated in MLIR->LLVMIR conversion and passed as the
|
||||
second argument to the init region. Therefore the type of arguments to
|
||||
the regions should be a type which represents a pointer to `type`.
|
||||
|
||||
The `locality_specifier_type` attribute specifies whether the localized
|
||||
corresponds to a `local` or a `local_init` specifier.
|
||||
}];
|
||||
|
||||
let arguments = (ins SymbolNameAttr:$sym_name,
|
||||
TypeAttrOf<AnyType>:$type,
|
||||
LocalitySpecifierTypeAttr:$locality_specifier_type);
|
||||
|
||||
let regions = (region AnyRegion:$init_region,
|
||||
AnyRegion:$copy_region,
|
||||
AnyRegion:$dealloc_region);
|
||||
|
||||
let assemblyFormat = [{
|
||||
$locality_specifier_type $sym_name `:` $type
|
||||
(`init` $init_region^)?
|
||||
(`copy` $copy_region^)?
|
||||
(`dealloc` $dealloc_region^)?
|
||||
attr-dict
|
||||
}];
|
||||
|
||||
let builders = [
|
||||
OpBuilder<(ins CArg<"mlir::TypeRange">:$result,
|
||||
CArg<"mlir::StringAttr">:$sym_name,
|
||||
CArg<"mlir::TypeAttr">:$type)>
|
||||
];
|
||||
|
||||
let extraClassDeclaration = [{
|
||||
/// Get the type for arguments to nested regions. This should
|
||||
/// generally be either the same as getType() or some pointer
|
||||
/// type (pointing to the type allocated by this op).
|
||||
/// This method will return Type{nullptr} if there are no nested
|
||||
/// regions.
|
||||
mlir::Type getArgType() {
|
||||
for (mlir::Region *region : getRegions())
|
||||
for (mlir::Type ty : region->getArgumentTypes())
|
||||
return ty;
|
||||
return nullptr;
|
||||
}
|
||||
}];
|
||||
|
||||
let hasRegionVerifier = 1;
|
||||
}
|
||||
|
||||
def fir_DoConcurrentOp : fir_Op<"do_concurrent",
|
||||
[SingleBlock, AutomaticAllocationScope]> {
|
||||
let summary = "do concurrent loop wrapper";
|
||||
|
@ -4909,6 +4909,105 @@ void fir::BoxTotalElementsOp::getCanonicalizationPatterns(
|
||||
patterns.add<SimplifyBoxTotalElementsOp>(context);
|
||||
}
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
// LocalitySpecifierOp
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
llvm::LogicalResult fir::LocalitySpecifierOp::verifyRegions() {
|
||||
mlir::Type argType = getArgType();
|
||||
auto verifyTerminator = [&](mlir::Operation *terminator,
|
||||
bool yieldsValue) -> llvm::LogicalResult {
|
||||
if (!terminator->getBlock()->getSuccessors().empty())
|
||||
return llvm::success();
|
||||
|
||||
if (!llvm::isa<fir::YieldOp>(terminator))
|
||||
return mlir::emitError(terminator->getLoc())
|
||||
<< "expected exit block terminator to be an `fir.yield` op.";
|
||||
|
||||
YieldOp yieldOp = llvm::cast<YieldOp>(terminator);
|
||||
mlir::TypeRange yieldedTypes = yieldOp.getResults().getTypes();
|
||||
|
||||
if (!yieldsValue) {
|
||||
if (yieldedTypes.empty())
|
||||
return llvm::success();
|
||||
|
||||
return mlir::emitError(terminator->getLoc())
|
||||
<< "Did not expect any values to be yielded.";
|
||||
}
|
||||
|
||||
if (yieldedTypes.size() == 1 && yieldedTypes.front() == argType)
|
||||
return llvm::success();
|
||||
|
||||
auto error = mlir::emitError(yieldOp.getLoc())
|
||||
<< "Invalid yielded value. Expected type: " << argType
|
||||
<< ", got: ";
|
||||
|
||||
if (yieldedTypes.empty())
|
||||
error << "None";
|
||||
else
|
||||
error << yieldedTypes;
|
||||
|
||||
return error;
|
||||
};
|
||||
|
||||
auto verifyRegion = [&](mlir::Region ®ion, unsigned expectedNumArgs,
|
||||
llvm::StringRef regionName,
|
||||
bool yieldsValue) -> llvm::LogicalResult {
|
||||
assert(!region.empty());
|
||||
|
||||
if (region.getNumArguments() != expectedNumArgs)
|
||||
return mlir::emitError(region.getLoc())
|
||||
<< "`" << regionName << "`: "
|
||||
<< "expected " << expectedNumArgs
|
||||
<< " region arguments, got: " << region.getNumArguments();
|
||||
|
||||
for (mlir::Block &block : region) {
|
||||
// MLIR will verify the absence of the terminator for us.
|
||||
if (!block.mightHaveTerminator())
|
||||
continue;
|
||||
|
||||
if (failed(verifyTerminator(block.getTerminator(), yieldsValue)))
|
||||
return llvm::failure();
|
||||
}
|
||||
|
||||
return llvm::success();
|
||||
};
|
||||
|
||||
// Ensure all of the region arguments have the same type
|
||||
for (mlir::Region *region : getRegions())
|
||||
for (mlir::Type ty : region->getArgumentTypes())
|
||||
if (ty != argType)
|
||||
return emitError() << "Region argument type mismatch: got " << ty
|
||||
<< " expected " << argType << ".";
|
||||
|
||||
mlir::Region &initRegion = getInitRegion();
|
||||
if (!initRegion.empty() &&
|
||||
failed(verifyRegion(getInitRegion(), /*expectedNumArgs=*/2, "init",
|
||||
/*yieldsValue=*/true)))
|
||||
return llvm::failure();
|
||||
|
||||
LocalitySpecifierType dsType = getLocalitySpecifierType();
|
||||
|
||||
if (dsType == LocalitySpecifierType::Local && !getCopyRegion().empty())
|
||||
return emitError("`local` specifiers do not require a `copy` region.");
|
||||
|
||||
if (dsType == LocalitySpecifierType::LocalInit && getCopyRegion().empty())
|
||||
return emitError(
|
||||
"`local_init` specifiers require at least a `copy` region.");
|
||||
|
||||
if (dsType == LocalitySpecifierType::LocalInit &&
|
||||
failed(verifyRegion(getCopyRegion(), /*expectedNumArgs=*/2, "copy",
|
||||
/*yieldsValue=*/true)))
|
||||
return llvm::failure();
|
||||
|
||||
if (!getDeallocRegion().empty() &&
|
||||
failed(verifyRegion(getDeallocRegion(), /*expectedNumArgs=*/1, "dealloc",
|
||||
/*yieldsValue=*/false)))
|
||||
return llvm::failure();
|
||||
|
||||
return llvm::success();
|
||||
}
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
// DoConcurrentOp
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
@ -90,3 +90,22 @@ func.func @dc_2d_reduction(%i_lb: index, %i_ub: index, %i_st: index,
|
||||
// CHECK: fir.store %[[J_IV_CVT]] to %[[J]] : !fir.ref<i32>
|
||||
// CHECK: }
|
||||
// CHECK: }
|
||||
|
||||
|
||||
fir.local {type = local} @local_privatizer : i32
|
||||
|
||||
// CHECK: fir.local {type = local} @[[LOCAL_PRIV_SYM:local_privatizer]] : i32
|
||||
|
||||
fir.local {type = local_init} @local_init_privatizer : i32 copy {
|
||||
^bb0(%arg0: !fir.ref<i32>, %arg1: !fir.ref<i32>):
|
||||
%0 = fir.load %arg0 : !fir.ref<i32>
|
||||
fir.store %0 to %arg1 : !fir.ref<i32>
|
||||
fir.yield(%arg1 : !fir.ref<i32>)
|
||||
}
|
||||
|
||||
// CHECK: fir.local {type = local_init} @[[LOCAL_INIT_PRIV_SYM:local_init_privatizer]] : i32
|
||||
// CHECK: ^bb0(%[[ORIG_VAL:.*]]: !fir.ref<i32>, %[[LOCAL_VAL:.*]]: !fir.ref<i32>):
|
||||
// CHECK: %[[ORIG_VAL_LD:.*]] = fir.load %[[ORIG_VAL]]
|
||||
// CHECK: fir.store %[[ORIG_VAL_LD]] to %[[LOCAL_VAL]] : !fir.ref<i32>
|
||||
// CHECK: fir.yield(%[[LOCAL_VAL]] : !fir.ref<i32>)
|
||||
// CHECK: }
|
||||
|
@ -1,5 +1,3 @@
|
||||
|
||||
|
||||
// RUN: fir-opt -split-input-file -verify-diagnostics --strict-fir-volatile-verifier %s
|
||||
|
||||
// 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> {
|
||||
%0 = fir.volatile_cast %arg0 : (!fir.ref<i32>) -> !fir.ref<i64>
|
||||
return %0 : !fir.ref<i64>
|
||||
}
|
||||
|
||||
// -----
|
||||
|
||||
fir.local {type = local} @x.localizer : i32 init {
|
||||
^bb0(%arg0: i32, %arg1: i32):
|
||||
%0 = arith.constant 0.0 : f32
|
||||
// expected-error @below {{Invalid yielded value. Expected type: 'i32', got: 'f32'}}
|
||||
fir.yield(%0 : f32)
|
||||
}
|
||||
|
||||
// -----
|
||||
|
||||
// expected-error @below {{Region argument type mismatch: got 'f32' expected 'i32'.}}
|
||||
fir.local {type = local} @x.localizer : i32 init {
|
||||
^bb0(%arg0: i32, %arg1: f32):
|
||||
fir.yield
|
||||
}
|
||||
|
||||
// -----
|
||||
|
||||
fir.local {type = local} @x.localizer : f32 init {
|
||||
^bb0(%arg0: f32, %arg1: f32):
|
||||
fir.yield(%arg0: f32)
|
||||
} dealloc {
|
||||
^bb0(%arg0: f32):
|
||||
// expected-error @below {{Did not expect any values to be yielded.}}
|
||||
fir.yield(%arg0 : f32)
|
||||
}
|
||||
|
||||
// -----
|
||||
|
||||
fir.local {type = local} @x.localizer : i32 init {
|
||||
^bb0(%arg0: i32, %arg1: i32):
|
||||
// expected-error @below {{expected exit block terminator to be an `fir.yield` op.}}
|
||||
fir.unreachable
|
||||
}
|
||||
|
||||
// -----
|
||||
|
||||
// expected-error @below {{`init`: expected 2 region arguments, got: 1}}
|
||||
fir.local {type = local} @x.localizer : f32 init {
|
||||
^bb0(%arg0: f32):
|
||||
fir.yield(%arg0 : f32)
|
||||
}
|
||||
|
||||
// -----
|
||||
|
||||
// expected-error @below {{`copy`: expected 2 region arguments, got: 1}}
|
||||
fir.local {type = local_init} @x.privatizer : f32 copy {
|
||||
^bb0(%arg0: f32):
|
||||
fir.yield(%arg0 : f32)
|
||||
}
|
||||
|
||||
// -----
|
||||
|
||||
// expected-error @below {{`dealloc`: expected 1 region arguments, got: 2}}
|
||||
fir.local {type = local} @x.localizer : f32 dealloc {
|
||||
^bb0(%arg0: f32, %arg1: f32):
|
||||
fir.yield
|
||||
}
|
||||
|
||||
// -----
|
||||
|
||||
// expected-error @below {{`local` specifiers do not require a `copy` region.}}
|
||||
fir.local {type = local} @x.localizer : f32 copy {
|
||||
^bb0(%arg0: f32, %arg1 : f32):
|
||||
fir.yield(%arg0 : f32)
|
||||
}
|
||||
|
||||
// -----
|
||||
|
||||
// expected-error @below {{`local_init` specifiers require at least a `copy` region.}}
|
||||
fir.local {type = local_init} @x.localizer : f32 init {
|
||||
^bb0(%arg0: f32, %arg1: f32):
|
||||
fir.yield(%arg0 : f32)
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user