Skip to content

Commit db7ae8c

Browse files
shraiyshjeanPerier
authored andcommitted
[MLIR][OpenMP] Added omp.atomic.read and omp.atomic.write
This patch supports the atomic construct (read and write) following section 2.17.7 of OpenMP 5.0 standard. Also added tests and verifier for the same. Reviewed By: kiranchandramohan Differential Revision: https://reviews.llvm.org/D111992
1 parent 92ee602 commit db7ae8c

File tree

7 files changed

+303
-4
lines changed

7 files changed

+303
-4
lines changed

clang/lib/CodeGen/CGStmtOpenMP.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5991,6 +5991,7 @@ static void emitOMPAtomicExpr(CodeGenFunction &CGF, OpenMPClauseKind Kind,
59915991
case OMPC_when:
59925992
case OMPC_adjust_args:
59935993
case OMPC_append_args:
5994+
case OMPC_memory_order:
59945995
llvm_unreachable("Clause is not allowed in 'omp atomic'.");
59955996
}
59965997
}

flang/lib/Semantics/check-omp-structure.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1479,6 +1479,7 @@ CHECK_SIMPLE_CLAUSE(Filter, OMPC_filter)
14791479
CHECK_SIMPLE_CLAUSE(When, OMPC_when)
14801480
CHECK_SIMPLE_CLAUSE(AdjustArgs, OMPC_adjust_args)
14811481
CHECK_SIMPLE_CLAUSE(AppendArgs, OMPC_append_args)
1482+
CHECK_SIMPLE_CLAUSE(MemoryOrder, OMPC_memory_order)
14821483

14831484
CHECK_REQ_SCALAR_INT_CLAUSE(Grainsize, OMPC_grainsize)
14841485
CHECK_REQ_SCALAR_INT_CLAUSE(NumTasks, OMPC_num_tasks)

llvm/include/llvm/Frontend/OpenMP/OMP.td

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,26 @@ def OMPC_Schedule : Clause<"schedule"> {
144144
];
145145
}
146146

147+
def OMP_MEMORY_ORDER_SeqCst : ClauseVal<"seq_cst", 1, 1> {}
148+
def OMP_MEMORY_ORDER_AcqRel : ClauseVal<"acq_rel", 2, 1> {}
149+
def OMP_MEMORY_ORDER_Acquire : ClauseVal<"acquire", 3, 1> {}
150+
def OMP_MEMORY_ORDER_Release : ClauseVal<"release", 4, 1> {}
151+
def OMP_MEMORY_ORDER_Relaxed : ClauseVal<"relaxed", 5, 1> {}
152+
def OMP_MEMORY_ORDER_Default : ClauseVal<"default", 6, 0> {
153+
let isDefault = 1;
154+
}
155+
def OMPC_MemoryOrder : Clause<"memory_order"> {
156+
let enumClauseValue = "MemoryOrderKind";
157+
let allowedClauseValues = [
158+
OMP_MEMORY_ORDER_SeqCst,
159+
OMP_MEMORY_ORDER_AcqRel,
160+
OMP_MEMORY_ORDER_Acquire,
161+
OMP_MEMORY_ORDER_Release,
162+
OMP_MEMORY_ORDER_Relaxed,
163+
OMP_MEMORY_ORDER_Default
164+
];
165+
}
166+
147167
def OMPC_Ordered : Clause<"ordered"> {
148168
let clangClass = "OMPOrderedClause";
149169
let flangClass = "ScalarIntConstantExpr";

mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -502,6 +502,37 @@ def TaskwaitOp : OpenMP_Op<"taskwait"> {
502502
let assemblyFormat = "attr-dict";
503503
}
504504

505+
//===----------------------------------------------------------------------===//
506+
// 2.17.7 atomic construct
507+
//===----------------------------------------------------------------------===//
508+
509+
// In the OpenMP Specification, atomic construct has an `atomic-clause` which
510+
// can take the values `read`, `write`, `update` and `capture`. These four
511+
// kinds of atomic constructs are fundamentally independent and are handled
512+
// separately while lowering. Having four separate operations (one for each
513+
// value of the clause) here decomposes handling of this construct into a
514+
// two-step process.
515+
516+
def AtomicReadOp : OpenMP_Op<"atomic.read"> {
517+
let arguments = (ins OpenMP_PointerLikeType:$address,
518+
DefaultValuedAttr<I64Attr, "0">:$hint,
519+
OptionalAttr<MemoryOrderKind>:$memory_order);
520+
let results = (outs AnyType);
521+
let parser = [{ return parseAtomicReadOp(parser, result); }];
522+
let printer = [{ return printAtomicReadOp(p, *this); }];
523+
let verifier = [{ return verifyAtomicReadOp(*this); }];
524+
}
525+
526+
def AtomicWriteOp : OpenMP_Op<"atomic.write"> {
527+
let arguments = (ins OpenMP_PointerLikeType:$address,
528+
AnyType:$value,
529+
DefaultValuedAttr<I64Attr, "0">:$hint,
530+
OptionalAttr<MemoryOrderKind>:$memory_order);
531+
let parser = [{ return parseAtomicWriteOp(parser, result); }];
532+
let printer = [{ return printAtomicWriteOp(p, *this); }];
533+
let verifier = [{ return verifyAtomicWriteOp(*this); }];
534+
}
535+
505536
//===----------------------------------------------------------------------===//
506537
// 2.19.5.7 declare reduction Directive
507538
//===----------------------------------------------------------------------===//

mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp

Lines changed: 120 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -396,8 +396,9 @@ static LogicalResult verifyReductionVarList(Operation *op,
396396
///
397397
/// hint-clause = `hint` `(` hint-value `)`
398398
static ParseResult parseSynchronizationHint(OpAsmParser &parser,
399-
IntegerAttr &hintAttr) {
400-
if (failed(parser.parseOptionalKeyword("hint"))) {
399+
IntegerAttr &hintAttr,
400+
bool parseKeyword = true) {
401+
if (parseKeyword && failed(parser.parseOptionalKeyword("hint"))) {
401402
hintAttr = IntegerAttr::get(parser.getBuilder().getI64Type(), 0);
402403
return success();
403404
}
@@ -455,11 +456,11 @@ static void printSynchronizationHint(OpAsmPrinter &p, Operation *op,
455456

456457
p << "hint(";
457458
llvm::interleaveComma(hints, p);
458-
p << ")";
459+
p << ") ";
459460
}
460461

461462
/// Verifies a synchronization hint clause
462-
static LogicalResult verifySynchronizationHint(Operation *op, int32_t hint) {
463+
static LogicalResult verifySynchronizationHint(Operation *op, uint64_t hint) {
463464

464465
// Helper function to get n-th bit from the right end of `value`
465466
auto bitn = [](int value, int n) -> bool { return value & (1 << n); };
@@ -497,6 +498,8 @@ enum ClauseType {
497498
orderClause,
498499
orderedClause,
499500
inclusiveClause,
501+
memoryOrderClause,
502+
hintClause,
500503
COUNT
501504
};
502505

@@ -725,6 +728,19 @@ static ParseResult parseClauses(OpAsmParser &parser, OperationState &result,
725728
return failure();
726729
auto attr = UnitAttr::get(parser.getBuilder().getContext());
727730
result.addAttribute("inclusive", attr);
731+
} else if (clauseKeyword == "memory_order") {
732+
StringRef memoryOrder;
733+
if (checkAllowed(memoryOrderClause) || parser.parseLParen() ||
734+
parser.parseKeyword(&memoryOrder) || parser.parseRParen())
735+
return failure();
736+
result.addAttribute("memory_order",
737+
parser.getBuilder().getStringAttr(memoryOrder));
738+
} else if (clauseKeyword == "hint") {
739+
IntegerAttr hint;
740+
if (checkAllowed(hintClause) ||
741+
parseSynchronizationHint(parser, hint, false))
742+
return failure();
743+
result.addAttribute("hint", hint);
728744
} else {
729745
return parser.emitError(parser.getNameLoc())
730746
<< clauseKeyword << " is not a valid clause";
@@ -1194,5 +1210,105 @@ static LogicalResult verifyOrderedRegionOp(OrderedRegionOp op) {
11941210
return success();
11951211
}
11961212

1213+
//===----------------------------------------------------------------------===//
1214+
// AtomicReadOp
1215+
//===----------------------------------------------------------------------===//
1216+
1217+
/// Parser for AtomicReadOp
1218+
///
1219+
/// operation ::= `omp.atomic.read` atomic-clause-list address `->` result-type
1220+
/// address ::= operand `:` type
1221+
static ParseResult parseAtomicReadOp(OpAsmParser &parser,
1222+
OperationState &result) {
1223+
OpAsmParser::OperandType address;
1224+
Type addressType;
1225+
SmallVector<ClauseType> clauses = {memoryOrderClause, hintClause};
1226+
SmallVector<int> segments;
1227+
1228+
if (parser.parseOperand(address) ||
1229+
parseClauses(parser, result, clauses, segments) ||
1230+
parser.parseColonType(addressType) ||
1231+
parser.resolveOperand(address, addressType, result.operands))
1232+
return failure();
1233+
1234+
SmallVector<Type> resultType;
1235+
if (parser.parseArrowTypeList(resultType))
1236+
return failure();
1237+
result.addTypes(resultType);
1238+
return success();
1239+
}
1240+
1241+
/// Printer for AtomicReadOp
1242+
static void printAtomicReadOp(OpAsmPrinter &p, AtomicReadOp op) {
1243+
p << " " << op.address() << " ";
1244+
if (op.memory_order())
1245+
p << "memory_order(" << op.memory_order().getValue() << ") ";
1246+
if (op.hintAttr())
1247+
printSynchronizationHint(p << " ", op, op.hintAttr());
1248+
p << ": " << op.address().getType() << " -> " << op.getType();
1249+
return;
1250+
}
1251+
1252+
/// Verifier for AtomicReadOp
1253+
static LogicalResult verifyAtomicReadOp(AtomicReadOp op) {
1254+
if (op.memory_order()) {
1255+
StringRef memOrder = op.memory_order().getValue();
1256+
if (memOrder.equals("acq_rel") || memOrder.equals("release"))
1257+
return op.emitError(
1258+
"memory-order must not be acq_rel or release for atomic reads");
1259+
}
1260+
return verifySynchronizationHint(op, op.hint());
1261+
}
1262+
1263+
//===----------------------------------------------------------------------===//
1264+
// AtomicWriteOp
1265+
//===----------------------------------------------------------------------===//
1266+
1267+
/// Parser for AtomicWriteOp
1268+
///
1269+
/// operation ::= `omp.atomic.write` atomic-clause-list operands
1270+
/// operands ::= address `,` value
1271+
/// address ::= operand `:` type
1272+
/// value ::= operand `:` type
1273+
static ParseResult parseAtomicWriteOp(OpAsmParser &parser,
1274+
OperationState &result) {
1275+
OpAsmParser::OperandType address, value;
1276+
Type addrType, valueType;
1277+
SmallVector<ClauseType> clauses = {memoryOrderClause, hintClause};
1278+
SmallVector<int> segments;
1279+
1280+
if (parser.parseOperand(address) || parser.parseComma() ||
1281+
parser.parseOperand(value) ||
1282+
parseClauses(parser, result, clauses, segments) ||
1283+
parser.parseColonType(addrType) || parser.parseComma() ||
1284+
parser.parseType(valueType) ||
1285+
parser.resolveOperand(address, addrType, result.operands) ||
1286+
parser.resolveOperand(value, valueType, result.operands))
1287+
return failure();
1288+
return success();
1289+
}
1290+
1291+
/// Printer for AtomicWriteOp
1292+
static void printAtomicWriteOp(OpAsmPrinter &p, AtomicWriteOp op) {
1293+
p << " " << op.address() << ", " << op.value() << " ";
1294+
if (op.memory_order())
1295+
p << "memory_order(" << op.memory_order() << ") ";
1296+
if (op.hintAttr())
1297+
printSynchronizationHint(p, op, op.hintAttr());
1298+
p << ": " << op.address().getType() << ", " << op.value().getType();
1299+
return;
1300+
}
1301+
1302+
/// Verifier for AtomicWriteOp
1303+
static LogicalResult verifyAtomicWriteOp(AtomicWriteOp op) {
1304+
if (op.memory_order()) {
1305+
StringRef memoryOrder = op.memory_order().getValue();
1306+
if (memoryOrder.equals("acq_rel") || memoryOrder.equals("acquire"))
1307+
return op.emitError(
1308+
"memory-order must not be acq_rel or acquire for atomic writes");
1309+
}
1310+
return verifySynchronizationHint(op, op.hint());
1311+
}
1312+
11971313
#define GET_OP_CLASSES
11981314
#include "mlir/Dialect/OpenMP/OpenMPOps.cpp.inc"

mlir/test/Dialect/OpenMP/invalid.mlir

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -375,3 +375,99 @@ func @omp_ordered5(%arg1 : i32, %arg2 : i32, %arg3 : i32, %vec0 : i64, %vec1 : i
375375
}
376376
return
377377
}
378+
379+
// -----
380+
381+
func @omp_atomic_read1(%addr : memref<i32>) {
382+
// expected-error @below {{the hints omp_sync_hint_nonspeculative and omp_sync_hint_speculative cannot be combined.}}
383+
%1 = omp.atomic.read %addr hint(speculative, nonspeculative) : memref<i32> -> i32
384+
return
385+
}
386+
387+
// -----
388+
389+
func @omp_atomic_read2(%addr : memref<i32>) {
390+
// expected-error @below {{attribute 'memory_order' failed to satisfy constraint: MemoryOrderKind Clause}}
391+
%1 = omp.atomic.read %addr memory_order(xyz) : memref<i32> -> i32
392+
return
393+
}
394+
395+
// -----
396+
397+
func @omp_atomic_read3(%addr : memref<i32>) {
398+
// expected-error @below {{memory-order must not be acq_rel or release for atomic reads}}
399+
%1 = omp.atomic.read %addr memory_order(acq_rel) : memref<i32> -> i32
400+
return
401+
}
402+
403+
// -----
404+
405+
func @omp_atomic_read4(%addr : memref<i32>) {
406+
// expected-error @below {{memory-order must not be acq_rel or release for atomic reads}}
407+
%1 = omp.atomic.read %addr memory_order(release) : memref<i32> -> i32
408+
return
409+
}
410+
411+
// -----
412+
413+
func @omp_atomic_read5(%addr : memref<i32>) {
414+
// expected-error @below {{at most one memory_order clause can appear on the omp.atomic.read operation}}
415+
%1 = omp.atomic.read %addr memory_order(acquire) memory_order(relaxed) : memref<i32> -> i32
416+
return
417+
}
418+
419+
// -----
420+
421+
func @omp_atomic_read6(%addr : memref<i32>) {
422+
// expected-error @below {{at most one hint clause can appear on the omp.atomic.read operation}}
423+
%1 = omp.atomic.read %addr hint(speculative) hint(contended) : memref<i32> -> i32
424+
return
425+
}
426+
427+
// -----
428+
429+
func @omp_atomic_write1(%addr : memref<i32>, %val : i32) {
430+
// expected-error @below {{the hints omp_sync_hint_uncontended and omp_sync_hint_contended cannot be combined}}
431+
omp.atomic.write %addr, %val hint(contended, uncontended) : memref<i32>, i32
432+
return
433+
}
434+
435+
// -----
436+
437+
func @omp_atomic_write2(%addr : memref<i32>, %val : i32) {
438+
// expected-error @below {{memory-order must not be acq_rel or acquire for atomic writes}}
439+
omp.atomic.write %addr, %val memory_order(acq_rel) : memref<i32>, i32
440+
return
441+
}
442+
443+
// -----
444+
445+
func @omp_atomic_write3(%addr : memref<i32>, %val : i32) {
446+
// expected-error @below {{memory-order must not be acq_rel or acquire for atomic writes}}
447+
omp.atomic.write %addr, %val memory_order(acquire) : memref<i32>, i32
448+
return
449+
}
450+
451+
// -----
452+
453+
func @omp_atomic_write4(%addr : memref<i32>, %val : i32) {
454+
// expected-error @below {{at most one memory_order clause can appear on the omp.atomic.write operation}}
455+
omp.atomic.write %addr, %val memory_order(release) memory_order(seq_cst) : memref<i32>, i32
456+
return
457+
}
458+
459+
// -----
460+
461+
func @omp_atomic_write5(%addr : memref<i32>, %val : i32) {
462+
// expected-error @below {{at most one hint clause can appear on the omp.atomic.write operation}}
463+
omp.atomic.write %addr, %val hint(contended) hint(speculative) : memref<i32>, i32
464+
return
465+
}
466+
467+
// -----
468+
469+
func @omp_atomic_write6(%addr : memref<i32>, %val : i32) {
470+
// expected-error @below {{attribute 'memory_order' failed to satisfy constraint: MemoryOrderKind Clause}}
471+
omp.atomic.write %addr, %val memory_order(xyz) : memref<i32>, i32
472+
return
473+
}

mlir/test/Dialect/OpenMP/ops.mlir

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -468,3 +468,37 @@ func @omp_ordered(%arg1 : i32, %arg2 : i32, %arg3 : i32,
468468

469469
return
470470
}
471+
472+
// CHECK-LABEL: omp_atomic_read
473+
// CHECK-SAME: (%[[ADDR:.*]]: memref<i32>)
474+
func @omp_atomic_read(%addr : memref<i32>) {
475+
// CHECK: %{{.*}} = omp.atomic.read %[[ADDR]] : memref<i32> -> i32
476+
%1 = omp.atomic.read %addr : memref<i32> -> i32
477+
// CHECK: %{{.*}} = omp.atomic.read %[[ADDR]] memory_order(seq_cst) : memref<i32> -> i32
478+
%2 = omp.atomic.read %addr memory_order(seq_cst) : memref<i32> -> i32
479+
// CHECK: %{{.*}} = omp.atomic.read %[[ADDR]] memory_order(acquire) : memref<i32> -> i32
480+
%5 = omp.atomic.read %addr memory_order(acquire) : memref<i32> -> i32
481+
// CHECK: %{{.*}} = omp.atomic.read %[[ADDR]] memory_order(relaxed) : memref<i32> -> i32
482+
%6 = omp.atomic.read %addr memory_order(relaxed) : memref<i32> -> i32
483+
// CHECK: %{{.*}} = omp.atomic.read %[[ADDR]] hint(contended, nonspeculative) : memref<i32> -> i32
484+
%7 = omp.atomic.read %addr hint(nonspeculative, contended) : memref<i32> -> i32
485+
// CHECK: %{{.*}} = omp.atomic.read %[[ADDR]] memory_order(seq_cst) hint(contended, speculative) : memref<i32> -> i32
486+
%8 = omp.atomic.read %addr hint(speculative, contended) memory_order(seq_cst) : memref<i32> -> i32
487+
return
488+
}
489+
490+
// CHECK-LABEL: omp_atomic_write
491+
// CHECK-SAME: (%[[ADDR:.*]]: memref<i32>, %[[VAL:.*]]: i32)
492+
func @omp_atomic_write(%addr : memref<i32>, %val : i32) {
493+
// CHECK: omp.atomic.write %[[ADDR]], %[[VAL]] : memref<i32>, i32
494+
omp.atomic.write %addr, %val : memref<i32>, i32
495+
// CHECK: omp.atomic.write %[[ADDR]], %[[VAL]] memory_order(seq_cst) : memref<i32>, i32
496+
omp.atomic.write %addr, %val memory_order(seq_cst) : memref<i32>, i32
497+
// CHECK: omp.atomic.write %[[ADDR]], %[[VAL]] memory_order(release) : memref<i32>, i32
498+
omp.atomic.write %addr, %val memory_order(release) : memref<i32>, i32
499+
// CHECK: omp.atomic.write %[[ADDR]], %[[VAL]] memory_order(relaxed) : memref<i32>, i32
500+
omp.atomic.write %addr, %val memory_order(relaxed) : memref<i32>, i32
501+
// CHECK: omp.atomic.write %[[ADDR]], %[[VAL]] hint(uncontended, speculative) : memref<i32>, i32
502+
omp.atomic.write %addr, %val hint(speculative, uncontended) : memref<i32>, i32
503+
return
504+
}

0 commit comments

Comments
 (0)