Files
gps/tools/local/circt/include/mlir/Dialect/OpenACC/OpenACCCGOps.td
T
2026-07-12 11:01:51 +03:00

586 lines
24 KiB
TableGen

//===- OpenACCCGOps.td - OpenACC intermediate operations ---*- tablegen -*-===//
//
// Part of the MLIR Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// Defines MLIR OpenACC intermediate operations used to represent semantics
// when decomposing and lowering `acc` dialect operations.
// These operations are placed in a separate file because they do not represent
// direct mappings of OpenACC language constructs.
//
//===----------------------------------------------------------------------===//
#ifndef OPENACC_CG_OPS
#define OPENACC_CG_OPS
include "mlir/Interfaces/InferTypeOpInterface.td"
include "mlir/Interfaces/ViewLikeInterface.td"
include "mlir/IR/CommonAttrConstraints.td"
// This file is intended to be included from OpenACCOps.td, which provides
// the necessary includes and definitions. The operations defined here use
// types and definitions from that file.
//===----------------------------------------------------------------------===//
// acc.reduction_init
//===----------------------------------------------------------------------===//
def OpenACC_ReductionInitOp
: OpenACC_Op<"reduction_init",
[SameOperandsAndResultType, RecursiveMemoryEffects,
DeclareOpInterfaceMethods<RegionBranchOpInterface,
["getRegionInvocationBounds",
"getSuccessorInputs"]>,
SingleBlockImplicitTerminator<"YieldOp">]> {
let summary = "Allocate and initialize a reduction variable from a recipe";
let description = [{
This operation provides materialized allocation and initialization for a
private reduction variable from an OpenACC reduction recipe. The region
contains the recipe's init code and must yield a single value (the private
reduction storage) via `acc.yield`.
The var operand is the original/shared reduction variable. The
reduction_operator specifies the reduction kind (e.g. add, mul).
}];
let arguments = (ins OpenACC_AnyPointerOrMappableType:$var,
OpenACC_ReductionOperatorAttr:$reductionOperator);
let results = (outs OpenACC_AnyPointerOrMappableType:$result);
let regions = (region AnyRegion:$region);
let assemblyFormat = [{
$var $reductionOperator `:` type($result) $region attr-dict
}];
let hasVerifier = 1;
}
//===----------------------------------------------------------------------===//
// acc.reduction_combine_region
//===----------------------------------------------------------------------===//
def OpenACC_ReductionCombineRegionOp
: OpenACC_Op<"reduction_combine_region",
[SameTypeOperands, RecursiveMemoryEffects,
DeclareOpInterfaceMethods<RegionBranchOpInterface,
["getRegionInvocationBounds",
"getSuccessorInputs"]>,
SingleBlockImplicitTerminator<"YieldOp">]> {
let summary = "Combine a reduction private value with its original (recipe)";
let description = [{
This operation provides materialized reduction combine code from an
OpenACC reduction recipe. The region takes the partially reduced value(s)
from the private reduction variable and combines them with the current
value(s) in the original/shared reduction variable. The region is
terminated by `acc.yield` with no operands.
The destVar operand is the original/shared reduction variable.
The srcVar operand is typically the result of acc.reduction_init.
}];
let arguments = (ins OpenACC_AnyPointerOrMappableType:$destVar,
OpenACC_AnyPointerOrMappableType:$srcVar);
let results = (outs);
let regions = (region AnyRegion:$region);
let assemblyFormat = [{
$srcVar `into` $destVar `:` type($srcVar) $region
attr-dict
}];
let hasVerifier = 1;
}
//===----------------------------------------------------------------------===//
// acc.reduction_combine
//===----------------------------------------------------------------------===//
def OpenACC_ReductionCombineOp: OpenACC_Op<"reduction_combine",
[SameTypeOperands, DeclareOpInterfaceMethods<MemoryEffectsOpInterface>]> {
let summary = "Combine a reduction partial sum with its original value";
let description = [{
This operation is a composite to do a typical update of a reduction
variable. The intention of this operator is to facilitate codegen
decisions (such as generate an atomic update). E.g.
```
acc.reduction_combine %src into %dest <addi> : memref<i32>
```
Might lower to something similar to
```
%loadSrc = memref.load %src[] : memref<i32>
%loadDest = memref.load %dest[] : memref<i32>
%combine = arith.addi %loadSrc, %loadDest : i32
memref.store %combine, %dest[] : memref<i32>
```
The $destMemref operand is a "pointer" to the original reduction
variable (typically shared). The $srcMemref operand is a "pointer"
to the partial sum of the reduction (typically private).
The $kind is the OpenACC reduction operator that determines how to
accumulate the two values.
}];
let arguments = (ins OpenACC_AnyPointerOrMappableType:$destMemref,
OpenACC_AnyPointerOrMappableType:$srcMemref,
OpenACC_ReductionOperatorAttr:$reductionOperator);
let assemblyFormat = [{
$srcMemref `into` $destMemref $reductionOperator `:` type($destMemref) attr-dict
}];
}
//===----------------------------------------------------------------------===//
// acc.reduction_accumulate
//===----------------------------------------------------------------------===//
def OpenACC_ReductionAccumulateOp
: OpenACC_Op<"reduction_accumulate", []> {
let summary = "Accumulate an SSA value into a reduction variable";
let description = [{
Accumulates a scalar SSA value into a pointer-like reduction variable.
Example:
```mlir
%private = memref.alloca() {acc.par_dims = #acc<par_dims[thread_x]>} : memref<i32>
memref.store %c0, %private[] : memref<i32>
%partial = scf.parallel (%iv) = (%c0) to (%cN) step (%c1) init (%c0) -> i32 {
%v = memref.load %data[%iv] : memref<Nxi32>
scf.reduce(%v : i32) {
^bb0(%lhs: i32, %rhs: i32):
%sum = arith.addi %lhs, %rhs : i32
scf.reduce.return %sum : i32
}
} {acc.par_dims = #acc<par_dims[thread_x]>}
acc.reduction_accumulate %partial to %private <add>
: i32 -> memref<i32> {par_dims = #acc<par_dims[thread_x]>}
acc.reduction_combine %private into %shared <add> : memref<i32>
{acc.par_dims = #acc<par_dims[thread_x]>}
```
}];
let arguments = (ins AnyTypeOf<[AnyInteger, AnyFloat, AnyComplex]>:$value,
Arg<OpenACC_PointerLikeType,
"Reduction variable to update",
[MemRead, MemWrite]>:$memref,
OpenACC_ReductionOperatorAttr:$reductionOperator,
OpenACC_GPUParallelDimsAttr:$par_dims);
let assemblyFormat = [{
$value `to` $memref $reductionOperator `:` type($value) `->` type($memref) attr-dict
}];
let hasVerifier = 1;
}
//===----------------------------------------------------------------------===//
// acc.kernel_environment
//===----------------------------------------------------------------------===//
def OpenACC_KernelEnvironmentOp
: OpenACC_Op<"kernel_environment",
[AttrSizedOperandSegments, RecursiveMemoryEffects, SingleBlock,
NoTerminator,
DeclareOpInterfaceMethods<RegionBranchOpInterface,
["getSuccessorInputs"]>,
MemoryEffects<[MemWrite<OpenACC_ConstructResource>,
MemRead<OpenACC_CurrentDeviceIdResource>]>]> {
let summary = "Decomposition of compute constructs to capture data mapping "
"and asynchronous behavior information";
let description = [{
The `acc.kernel_environment` operation represents a decomposition of
any OpenACC compute construct (acc.kernels, acc.parallel, or
acc.serial) that captures data mapping and asynchronous behavior:
- data clause operands
- async clause operands
- wait clause operands
This allows kernel execution parallelism and privatization to be
handled separately, facilitating eventual lowering to GPU dialect where
kernel launching and compute offloading are handled separately.
}];
let arguments = (ins
Variadic<AnyType>:$dataClauseOperands,
Optional<IntOrIndex>:$asyncOperand,
UnitAttr:$asyncOnly,
Optional<IntOrIndex>:$waitDevnum,
Variadic<IntOrIndex>:$waitOperands,
UnitAttr:$waitOnly);
let regions = (region SizedRegion<1>:$region);
let assemblyFormat = [{
oilist(
`dataOperands` `(` $dataClauseOperands `:` type($dataClauseOperands) `)`
| `async` `` custom<OperandWithKeywordOnly>($asyncOperand,
type($asyncOperand), $asyncOnly)
| `wait_devnum` `(` $waitDevnum `:` type($waitDevnum) `)`
| `wait` `` custom<OperandsWithKeywordOnly>($waitOperands,
type($waitOperands), $waitOnly)
)
$region attr-dict
}];
let hasCanonicalizer = 1;
let hasVerifier = 1;
let extraClassDeclaration = [{
/// Create a `KernelEnvironmentOp` populated with data mapping and the
/// async/wait clauses for `deviceType` extracted from the given ACC compute
/// construct. Emplaces a block in the region and sets the rewriter's
/// insertion point to the start of that block so callers can create
/// operations inside it (e.g., `acc.compute_region`).
template <typename ComputeConstructT>
static KernelEnvironmentOp createAndPopulate(
ComputeConstructT computeConstruct, ::mlir::acc::DeviceType deviceType,
::mlir::OpBuilder &builder);
}];
}
//===----------------------------------------------------------------------===//
// acc.firstprivate_map
//===----------------------------------------------------------------------===//
def OpenACC_FirstprivateMapInitialOp
: OpenACC_DataEntryOp<
"firstprivate_map", "mlir::acc::DataClause::acc_firstprivate", "", [],
(ins Arg<OpenACC_AnyPointerOrMappableType, "Host variable">:$var)> {
let summary = "Represents the mapping of the initial value for firstprivate "
"semantics.";
let description = [{
The `acc.firstprivate_map` operation is an intermediate representation
used during the decomposition of `acc.firstprivate` operations. It
represents the mapping of the initial value from the host to the device,
which is then used to initialize per-thread private copies.
This operation is distinct from `acc.copyin` because:
- `acc.copyin` includes present counter updates, but private variables
do not impact reference counters
- The mapped value is used to initialize private copies rather than
being accessed directly
}];
let results = (outs Arg<OpenACC_AnyPointerOrMappableType,
"Accelerator mapped variable">:$accVar);
let extraClassDeclaration = extraClassDeclarationBase;
}
//===----------------------------------------------------------------------===//
// acc.privatize
//===----------------------------------------------------------------------===//
def OpenACC_PrivatizeOp : OpenACC_Op<"privatize", []> {
let summary = "Create a handle for privatized storage along parallel dimensions";
let description = [{
Introduces a privatization handle for storage that varies across the active
parallel dimensions (for example OpenACC `private` / `firstprivate` after
recipe materialization). The handle type is `acc.private_type<T>` where
`T` is the logical storage type (commonly a `memref`).
Optional `index` operands supply dynamic sizes when the privatized shape
depends on SSA values (for example `memref<?xi32>` row lengths).
The optional `acc.par_dims` attribute records which GPU parallel dimensions
participate in the privatization.
}];
let arguments = (ins Variadic<Index>:$dynamicSizes,
OptionalAttr<OpenACC_GPUParallelDimsAttr>:$par_dims);
let results = (outs OpenACC_PrivateType:$result);
let assemblyFormat = [{
(`(` $dynamicSizes^ `)`)? (`[` qualified($par_dims)^ `]`)? attr-dict `:` functional-type(operands, results)
}];
let builders = [
OpBuilder<(ins "::mlir::Type":$resultType,
CArg<"::mlir::ValueRange", "{}">:$dynamicSizes), [{
$_state.addTypes(resultType);
$_state.addOperands(dynamicSizes);
}]>
];
}
//===----------------------------------------------------------------------===//
// acc.unwrap_private
//===----------------------------------------------------------------------===//
def OpenACC_UnwrapPrivateOp
: OpenACC_Op<"unwrap_private",
[NoMemoryEffect, AlwaysSpeculatable, ViewLikeOpInterface]> {
let summary = "Unwrap acc.private_type handle to pointer-like storage";
let description = [{
Converts a privatization handle (`acc.private_type<T>`) to a pointer-like
view of the underlying private storage.
}];
let arguments = (ins OpenACC_PrivateType:$handle);
let results = (outs OpenACC_PointerLikeType:$result);
let assemblyFormat = [{
$handle attr-dict `:` qualified(type($handle)) `to` type($result)
}];
let extraClassDeclaration = [{
::mlir::Value getViewSource() { return getHandle(); }
}];
}
//===----------------------------------------------------------------------===//
// acc.private_local
//===----------------------------------------------------------------------===//
def OpenACC_PrivateLocalOp
: OpenACC_Op<"private_local", [NoMemoryEffect, AlwaysSpeculatable]> {
let summary = "Materialize privatized storage for the current parallelism context";
let description = [{
Given a value of type `acc.private_type<T>`, materializes the underlying
storage (typically a `memref`) for the current thread / iteration. Which
slice of the privatized allocation is selected is determined by surrounding
parallelism assigned in the context.
The result type is usually `T` (often a `memref` that matches the logical
storage type). The result may instead use a different surface type that
still aliases the same underlying storage as `T`; in that case passes that
consume this operation must treat the handle type and the result type as
describing the same storage layout.
}];
let arguments = (ins OpenACC_PrivateType:$privatized);
let results = (outs AnyType:$output);
let assemblyFormat = [{
$privatized attr-dict `:` functional-type(operands, results)
}];
let builders = [
OpBuilder<(ins "::mlir::Type":$resultType, "::mlir::Value":$privatized), [{
$_state.addOperands(privatized);
$_state.addTypes(resultType);
}]>
];
}
//===----------------------------------------------------------------------===//
// acc.par_width
//===----------------------------------------------------------------------===//
def OpenACC_ParWidthOp
: OpenACC_Op<"par_width", [NoMemoryEffect, AlwaysSpeculatable]> {
let summary = "Specify parallel width for a GPU dimension";
let description = [{
The `acc.par_width` operation specifies the parallel width for a
given GPU parallel dimension. It is used as an input to
`acc.compute_region` to define the launch configuration.
The optional `launchArg` operand provides a known width value. When
absent, the width is unknown and must be determined later (either at
compile time by analysis or at runtime).
Examples:
```mlir
// Known width from SSA value
%w1 = acc.par_width %vector_len {par_dim = #acc.par_dim<thread_x>}
// Unknown width (to be computed later)
%w2 = acc.par_width {par_dim = #acc.par_dim<block_x>}
```
}];
let arguments = (ins Optional<Index>:$launchArg,
OpenACC_GPUParallelDimAttr:$par_dim);
let results = (outs Index:$output);
let assemblyFormat = [{
($launchArg^)? attr-dict
}];
}
//===----------------------------------------------------------------------===//
// acc.compute_region
//===----------------------------------------------------------------------===//
// Local type constraint for gpu::AsyncTokenType.
def OpenACC_GPUAsyncTokenType : Type<
CPred<"::llvm::isa<::mlir::gpu::AsyncTokenType>($_self)">,
"GPU async token type">;
def OpenACC_ComputeRegionOp
: OpenACC_Op<"compute_region",
[OffloadRegionOpInterface, AffineScope,
RecursiveMemoryEffects,
SingleBlockImplicitTerminator<"YieldOp">,
IsolatedFromAbove, AttrSizedOperandSegments]> {
let summary = "Compute region for GPU execution";
let description = [{
The `acc.compute_region` operation wraps a region of code that will be
compiled and executed on a GPU. It is typically produced by lowering
OpenACC compute constructs (`acc.parallel`, `acc.kernels`, `acc.serial`)
but can also be targeted directly by other frontends or lowered from
other constructs that benefit from the automatic parallelization and data
mapping facilities that the `acc` dialect provides. It serves as the
bridge between the high-level representation and the `gpu.launch`
operation.
The operation is `IsolatedFromAbove`: all values used inside the
region must be explicitly captured. Values are captured in two ways:
- Launch arguments (`launch`): Results of `acc.par_width`
operations that define the parallel launch configuration. These
become `index`-typed block arguments representing the parallel
width for each dimension.
- Input arguments (`ins`): Arbitrary values captured from outside
the region (data pointers, scalars, etc.). These become block
arguments with their original types.
The `origin` attribute records which construct produced this compute
region (e.g., `"acc.parallel"`, `"acc.kernels"`). This is intended to
be solely informational.
Canonicalization may simplify `ins` captures: duplicate `ins` operands
(same SSA value threaded more than once) are merged by reusing the first
block argument, and unused `ins` operands (block arguments with no uses)
are removed. `launch` operands are never merged or dropped.
Example:
```mlir
%w0 = acc.par_width %c128 {par_dim = #acc.par_dim<thread_x>}
%w1 = acc.par_width %c8 {par_dim = #acc.par_dim<block_x>}
acc.compute_region launch(%arg0 = %w0, %arg1 = %w1)
ins(%arg2 = %data) : (memref<1024xf32>) {
%c0 = arith.constant 0 : index
%c1 = arith.constant 1 : index
%c1024 = arith.constant 1024 : index
scf.parallel (%iv) = (%c0) to (%c1024) step (%c1) {
%v = memref.load %arg2[%iv] : memref<1024xf32>
scf.reduce
} {acc.par_dims = #acc<par_dims[thread_x]>}
acc.yield
} {origin = "acc.parallel"}
```
}];
let arguments = (ins Variadic<Index>:$launchArgs,
Variadic<AnyType>:$inputArgs,
Optional<OpenACC_GPUAsyncTokenType>:$stream,
StrAttr:$origin,
OptionalAttr<FlatSymbolRefAttr>:$kernel_func_name,
OptionalAttr<FlatSymbolRefAttr>:$kernel_module_name);
let results = (outs Variadic<AnyType>:$results);
let regions = (region AnyRegion:$region);
let hasCanonicalizer = 1;
let extraClassDeclaration = [{
/// Look up the par_width op for the given dimension among launch args.
std::optional<mlir::Value> getLaunchArg(
::mlir::acc::GPUParallelDimAttr parDim);
/// Get the known (non-empty) launch value for a dimension.
std::optional<mlir::Value> getKnownLaunchArg(
::mlir::acc::GPUParallelDimAttr parDim);
/// Get the known constant launch value for a dimension.
std::optional<uint64_t> getKnownConstantLaunchArg(
::mlir::acc::GPUParallelDimAttr parDim);
/// Add a new input argument, appending to both the operand list and
/// the region block arguments. Returns the new block argument.
::mlir::BlockArgument appendInputArg(::mlir::Value);
/// After hoisting `value`'s defining op, wire it into this region: append it
/// as an `ins` operand, add the matching body entry argument, and replace
/// uses under this region with that argument (excluding this op's own `ins`
/// operand uses).
///
/// Requires that `value` is defined outside `getRegion()` and is still used
/// inside the region. Otherwise returns `std::nullopt`.
std::optional<::mlir::BlockArgument>
wireHoistedValueThroughIns(::mlir::Value value);
/// Check whether all parallel dimensions have width 1.
bool isEffectivelySerial();
/// Get the block argument representing the width for a given dimension.
::mlir::BlockArgument parDimToWidth(
::mlir::acc::GPUParallelDimAttr parDim);
/// Get the block argument for a specific gpu::Processor.
::mlir::BlockArgument gpuParWidth(::mlir::gpu::Processor);
/// Collect all GPU parallel dimensions present in the launch config.
llvm::SmallVector<::mlir::acc::GPUParallelDimAttr> getLaunchParDims();
/// Get the body block of the compute region.
::mlir::Block *getBody() { return &getRegion().front(); }
/// Get the terminator of the compute region.
::mlir::Operation *getTerminator() {
return &getRegion().back().back();
}
/// Return the `launch` or `ins` operand threaded to `blockArg`, or a null
/// `Value` if `blockArg` is not an argument of `getBody()` or its index is
/// out of range for this op's `launch` and `ins` operands.
::mlir::Value getOperand(::mlir::BlockArgument blockArg);
/// If `value` is a launch or input operand, return the body block argument
/// it is threaded through; otherwise `std::nullopt`. If `value` matches
/// more than one `ins` operand, the first match is returned (canonicalization
/// may merge duplicate `ins` values). Duplicate `launch` operands are not
/// folded.
std::optional<::mlir::BlockArgument> getBlockArg(::mlir::Value value);
}];
let hasVerifier = 1;
let hasCustomAssemblyFormat = 1;
}
//===----------------------------------------------------------------------===//
// acc.predicate_region
//===----------------------------------------------------------------------===//
def OpenACC_PredicateRegionOp
: OpenACC_Op<"predicate_region", [RecursiveMemoryEffects, NoTerminator]> {
let summary = "Groups operations at intermediate loop-nest points";
let description = [{
Groups statements within an `acc.compute_region` that sit at an
intermediate point in a loop nest (outside a partitioned loop body,
between or around nested loops). This grouping marks code whose execution
scope differs from that of surrounding partitioned loops, so predication
and synchronization can be applied correctly during lowering. Corresponds
to OpenACC single or redundant execution at nest transitions.
Example:
```mlir
// !$acc parallel num_gangs(NG) vector_length(VL)
// !$acc loop gang
// !$acc atomic update
// !$acc loop vector
// !$acc atomic update
%w_gang = acc.par_width %cNG {par_dim = #acc.par_dim<block_x>}
%w_vector = acc.par_width %cVL {par_dim = #acc.par_dim<thread_x>}
acc.compute_region launch(%ng = %w_gang, %vl = %w_vector)
ins(%arg_c1 = %c1, %arg_c2 = %c2) : (memref<i32>, memref<i32>) {
scf.parallel (%i) = (%c1) to (%cN) step (%c1) {
acc.predicate_region {
acc.atomic.update %arg_c1 : memref<i32> {
^bb0(%old: i32):
%one = arith.constant 1 : i32
%sum = arith.addi %old, %one : i32
acc.yield %sum : i32
}
}
scf.parallel (%j) = (%c1) to (%cN) step (%c1) {
acc.atomic.update %arg_c2 : memref<i32> {
^bb0(%old: i32):
%one = arith.constant 1 : i32
%sum = arith.addi %old, %one : i32
acc.yield %sum : i32
}
scf.reduce
} {acc.par_dims = #acc<par_dims[thread_x]>}
scf.reduce
} {acc.par_dims = #acc<par_dims[block_x]>}
acc.yield
} {origin = "acc.parallel"}
```
}];
let regions = (region AnyRegion:$region);
let assemblyFormat = "$region attr-dict";
let hasVerifier = 1;
}
#endif // OPENACC_CG_OPS