Skip to content

Commit 61278ec

Browse files
[openacc][openmp] Add dialect representation for acc atomic operations (#65493)
The OpenACC standard specifies an `atomic` construct in section 2.12 (of 3.3 spec), used to ensure that a specific location is accessed or updated atomically. Four different clauses are allowed: `read`, `write`, `update`, or `capture`. If no clause appears, it is as if `update` is used. The OpenMP specification defines the same clauses for `omp atomic`. The types of expression and the clauses in the OpenACC spec match the OpenMP spec exactly. The main difference is that the OpenMP specification is a superset - it includes clauses for `hint` and `memory order`. It also allows conditional expression statements. But otherwise, the expression definition matches. Thus, for OpenACC, we refactor and reuse the OpenMP implementation as follows: * The atomic operations are duplicated in OpenACC dialect. This is preferable so that each language's semantics are precisely represented even if specs have divergence. * However, since semantics overlap, a common interface between the atomic operations is being added. The semantics for the interfaces are not generic enough to be used outside of OpenACC and OpenMP, and thus new folders were added to hold common pieces of the two dialects. * The atomic interfaces define common accessors (such as getting `x` or `v`) which match the OpenMP and OpenACC specs. It also adds common verifiers intended to be called by each dialect's operation verifier. * The OpenMP write operation was updated to use `x` and `expr` to be consistent with its other operations (that use naming based on spec). The frontend lowering necessary to generate the dialect can also be reused. This will be done in a follow up change.
1 parent 24c5f18 commit 61278ec

File tree

23 files changed

+1159
-134
lines changed

23 files changed

+1159
-134
lines changed

mlir/include/mlir/Dialect/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ add_subdirectory(MemRef)
2222
add_subdirectory(MLProgram)
2323
add_subdirectory(NVGPU)
2424
add_subdirectory(OpenACC)
25+
add_subdirectory(OpenACCMPCommon)
2526
add_subdirectory(OpenMP)
2627
add_subdirectory(PDL)
2728
add_subdirectory(PDLInterp)

mlir/include/mlir/Dialect/OpenACC/OpenACC.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,14 @@
1616
#include "mlir/IR/BuiltinTypes.h"
1717
#include "mlir/IR/Dialect.h"
1818
#include "mlir/IR/OpDefinition.h"
19+
#include "mlir/IR/PatternMatch.h"
1920
#include "mlir/IR/SymbolTable.h"
2021

2122
#include "mlir/Bytecode/BytecodeOpInterface.h"
2223
#include "mlir/Dialect/OpenACC/OpenACCOpsDialect.h.inc"
2324
#include "mlir/Dialect/OpenACC/OpenACCOpsEnums.h.inc"
2425
#include "mlir/Dialect/OpenACC/OpenACCTypeInterfaces.h.inc"
26+
#include "mlir/Dialect/OpenACCMPCommon/Interfaces/AtomicInterfaces.h"
2527
#include "mlir/Interfaces/ControlFlowInterfaces.h"
2628
#include "mlir/Interfaces/SideEffectInterfaces.h"
2729

mlir/include/mlir/Dialect/OpenACC/OpenACCOps.td

Lines changed: 152 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ include "mlir/IR/SymbolInterfaces.td"
2222
include "mlir/Dialect/OpenACC/OpenACCBase.td"
2323
include "mlir/Dialect/OpenACC/OpenACCOpsTypes.td"
2424
include "mlir/Dialect/OpenACC/OpenACCTypeInterfaces.td"
25+
include "mlir/Dialect/OpenACCMPCommon/Interfaces/AtomicInterfaces.td"
2526

2627
// AccCommon requires definition of OpenACC_Dialect.
2728
include "mlir/Dialect/OpenACC/AccCommon.td"
@@ -1191,13 +1192,14 @@ def OpenACC_LoopOp : OpenACC_Op<"loop",
11911192

11921193
// Yield operation for the acc.loop and acc.parallel operations.
11931194
def OpenACC_YieldOp : OpenACC_Op<"yield", [ReturnLike, Terminator,
1194-
ParentOneOf<["FirstprivateRecipeOp, LoopOp, ParallelOp, PrivateRecipeOp, ReductionRecipeOp, SerialOp"]>]> {
1195+
ParentOneOf<["FirstprivateRecipeOp, LoopOp, ParallelOp, PrivateRecipeOp,"
1196+
"ReductionRecipeOp, SerialOp, AtomicUpdateOp"]>]> {
11951197
let summary = "Acc yield and termination operation";
11961198

11971199
let description = [{
11981200
`acc.yield` is a special terminator operation for block inside regions in
1199-
acc ops (parallel and loop). It returns values to the immediately enclosing
1200-
acc op.
1201+
various acc ops (including parallel, loop, atomic.update). It returns values
1202+
to the immediately enclosing acc op.
12011203
}];
12021204

12031205
let arguments = (ins Variadic<AnyType>:$operands);
@@ -1207,6 +1209,153 @@ def OpenACC_YieldOp : OpenACC_Op<"yield", [ReturnLike, Terminator,
12071209
let assemblyFormat = "attr-dict ($operands^ `:` type($operands))?";
12081210
}
12091211

1212+
//===----------------------------------------------------------------------===//
1213+
// 2.12 atomic construct
1214+
//===----------------------------------------------------------------------===//
1215+
1216+
def AtomicReadOp : OpenACC_Op<"atomic.read", [AllTypesMatch<["x", "v"]>,
1217+
AtomicReadOpInterface]> {
1218+
1219+
let summary = "performs an atomic read";
1220+
1221+
let description = [{
1222+
This operation performs an atomic read.
1223+
1224+
The operand `x` is the address from where the value is atomically read.
1225+
The operand `v` is the address where the value is stored after reading.
1226+
}];
1227+
1228+
let arguments = (ins OpenACC_PointerLikeType:$x,
1229+
OpenACC_PointerLikeType:$v,
1230+
TypeAttr:$element_type);
1231+
let assemblyFormat = [{
1232+
$v `=` $x
1233+
`:` type($x) `,` $element_type attr-dict
1234+
}];
1235+
let hasVerifier = 1;
1236+
}
1237+
1238+
def AtomicWriteOp : OpenACC_Op<"atomic.write",[AtomicWriteOpInterface]> {
1239+
1240+
let summary = "performs an atomic write";
1241+
1242+
let description = [{
1243+
This operation performs an atomic write.
1244+
1245+
The operand `x` is the address to where the `expr` is atomically
1246+
written w.r.t. multiple threads. The evaluation of `expr` need not be
1247+
atomic w.r.t. the write to address. In general, the type(x) must
1248+
dereference to type(expr).
1249+
}];
1250+
1251+
let arguments = (ins OpenACC_PointerLikeType:$x,
1252+
AnyType:$expr);
1253+
let assemblyFormat = [{
1254+
$x `=` $expr
1255+
`:` type($x) `,` type($expr)
1256+
attr-dict
1257+
}];
1258+
let hasVerifier = 1;
1259+
}
1260+
1261+
def AtomicUpdateOp : OpenACC_Op<"atomic.update",
1262+
[SingleBlockImplicitTerminator<"YieldOp">,
1263+
RecursiveMemoryEffects,
1264+
AtomicUpdateOpInterface]> {
1265+
1266+
let summary = "performs an atomic update";
1267+
1268+
let description = [{
1269+
This operation performs an atomic update.
1270+
1271+
The operand `x` is exactly the same as the operand `x` in the OpenACC
1272+
Standard (OpenACC 3.3, section 2.12). It is the address of the variable
1273+
that is being updated. `x` is atomically read/written.
1274+
1275+
The region describes how to update the value of `x`. It takes the value at
1276+
`x` as an input and must yield the updated value. Only the update to `x` is
1277+
atomic. Generally the region must have only one instruction, but can
1278+
potentially have more than one instructions too. The update is sematically
1279+
similar to a compare-exchange loop based atomic update.
1280+
1281+
The syntax of atomic update operation is different from atomic read and
1282+
atomic write operations. This is because only the host dialect knows how to
1283+
appropriately update a value. For example, while generating LLVM IR, if
1284+
there are no special `atomicrmw` instructions for the operation-type
1285+
combination in atomic update, a compare-exchange loop is generated, where
1286+
the core update operation is directly translated like regular operations by
1287+
the host dialect. The front-end must handle semantic checks for allowed
1288+
operations.
1289+
}];
1290+
1291+
let arguments = (ins Arg<OpenACC_PointerLikeType,
1292+
"Address of variable to be updated",
1293+
[MemRead, MemWrite]>:$x);
1294+
let regions = (region SizedRegion<1>:$region);
1295+
let assemblyFormat = [{
1296+
$x `:` type($x) $region attr-dict
1297+
}];
1298+
let hasVerifier = 1;
1299+
let hasRegionVerifier = 1;
1300+
let hasCanonicalizeMethod = 1;
1301+
let extraClassDeclaration = [{
1302+
Operation* getFirstOp() {
1303+
return &getRegion().front().getOperations().front();
1304+
}
1305+
}];
1306+
}
1307+
1308+
def AtomicCaptureOp : OpenACC_Op<"atomic.capture",
1309+
[SingleBlockImplicitTerminator<"TerminatorOp">,
1310+
RecursiveMemoryEffects, AtomicCaptureOpInterface]> {
1311+
let summary = "performs an atomic capture";
1312+
let description = [{
1313+
This operation performs an atomic capture.
1314+
1315+
The region has the following allowed forms:
1316+
1317+
```
1318+
acc.atomic.capture {
1319+
acc.atomic.update ...
1320+
acc.atomic.read ...
1321+
acc.terminator
1322+
}
1323+
1324+
acc.atomic.capture {
1325+
acc.atomic.read ...
1326+
acc.atomic.update ...
1327+
acc.terminator
1328+
}
1329+
1330+
acc.atomic.capture {
1331+
acc.atomic.read ...
1332+
acc.atomic.write ...
1333+
acc.terminator
1334+
}
1335+
```
1336+
1337+
}];
1338+
1339+
let regions = (region SizedRegion<1>:$region);
1340+
let assemblyFormat = [{
1341+
$region attr-dict
1342+
}];
1343+
let hasRegionVerifier = 1;
1344+
let extraClassDeclaration = [{
1345+
/// Returns the `atomic.read` operation inside the region, if any.
1346+
/// Otherwise, it returns nullptr.
1347+
AtomicReadOp getAtomicReadOp();
1348+
1349+
/// Returns the `atomic.write` operation inside the region, if any.
1350+
/// Otherwise, it returns nullptr.
1351+
AtomicWriteOp getAtomicWriteOp();
1352+
1353+
/// Returns the `atomic.update` operation inside the region, if any.
1354+
/// Otherwise, it returns nullptr.
1355+
AtomicUpdateOp getAtomicUpdateOp();
1356+
}];
1357+
}
1358+
12101359
//===----------------------------------------------------------------------===//
12111360
// 2.13 Declare Directive
12121361
//===----------------------------------------------------------------------===//
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
add_subdirectory(Interfaces)
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
//===- DirectiveAtomicInterfaces.h - directive atomic ops interfaces ------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
//
9+
// This file implements the operation interface for atomic operations used
10+
// in OpenACC and OpenMP.
11+
//
12+
//===----------------------------------------------------------------------===//
13+
14+
#ifndef OPENACC_MP_COMMON_INTERFACES_ATOMICINTERFACES_H_
15+
#define OPENACC_MP_COMMON_INTERFACES_ATOMICINTERFACES_H_
16+
17+
#include "mlir/IR/OpDefinition.h"
18+
#include "mlir/Interfaces/ControlFlowInterfaces.h"
19+
20+
#include "mlir/Dialect/OpenACCMPCommon/Interfaces/AtomicInterfaces.h.inc"
21+
22+
#endif // OPENACC_MP_COMMON_INTERFACES_ATOMICINTERFACES_H_

0 commit comments

Comments
 (0)