//===- DirectiveAtomicInterfaces.td - atomic interfaces ----*- tablegen -*-===// // // Part of the LLVM 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 the operation interface for atomic operations used in OpenACC and // OpenMP. // //===----------------------------------------------------------------------===// #ifndef OPENACC_MP_COMMON_INTERFACES_ATOMICINTERFACES #define OPENACC_MP_COMMON_INTERFACES_ATOMICINTERFACES include "mlir/IR/OpBase.td" include "mlir/Interfaces/ControlFlowInterfaces.td" def AtomicReadOpInterface : OpInterface<"AtomicReadOpInterface"> { let description = [{ This interface is used for OpenACC/OpenMP dialect operation that performs an atomic read. The interface terminology uses `x` and `v` like the directive specifications: `v = x;` `x` is the address from where the value is atomically read. `v` is the address where the value is stored after reading. }]; let cppNamespace = "::mlir::accomp"; let methods = [ InterfaceMethod<[{ Common verifier for operation that implements atomic read interface. }], /*retTy=*/"::llvm::LogicalResult", /*methodName=*/"verifyCommon", /*args=*/(ins), /*methodBody=*/"", /*defaultImplementation=*/[{ if ($_op.getX() == $_op.getV()) { return $_op.emitError( "read and write must not be to the same location for atomic reads"); } return mlir::success(); }] >, InterfaceMethod<[{ Obtains `x` which is the address from where the value is atomically read. }], /*retTy=*/"::mlir::Value", /*methodName=*/"getX", /*args=*/(ins) >, InterfaceMethod<[{ Obtains `v` which is the address where the value is stored after reading. }], /*retTy=*/"::mlir::Value", /*methodName=*/"getV", /*args=*/(ins) >, ]; } def AtomicWriteOpInterface : OpInterface<"AtomicWriteOpInterface"> { let description = [{ This interface is used for OpenACC/OpenMP dialect operation that performs an atomic write. The interface terminology uses `x` and `expr` like the directive specifications: `x = expr;` `x` is the address to where the `expr` is atomically written. }]; let cppNamespace = "::mlir::accomp"; let methods = [ InterfaceMethod<[{ Common verifier for operation that implements atomic write interface. }], /*retTy=*/"::llvm::LogicalResult", /*methodName=*/"verifyCommon", /*args=*/(ins), /*methodBody=*/"", /*defaultImplementation=*/[{ mlir::Type elementType = $_op.getX().getType().getElementType(); if (elementType && elementType != $_op.getExpr().getType()) return $_op.emitError("address must dereference to value type"); return mlir::success(); }] >, InterfaceMethod<[{ Obtains `x` which is the address to which the value is atomically written to. }], /*retTy=*/"::mlir::Value", /*methodName=*/"getX", /*args=*/(ins) >, InterfaceMethod<[{ Obtains `expr` which corresponds to the expression whose value is written to `x`. }], /*retTy=*/"::mlir::Value", /*methodName=*/"getExpr", /*args=*/(ins) >, ]; } def AtomicUpdateOpInterface : OpInterface<"AtomicUpdateOpInterface"> { let description = [{ This interface is used for OpenACC/OpenMP dialect operation that performs an atomic update. The interface terminology uses `x` to specify the address where a value is atomically written/read. Since atomic update expression comes in many forms, this interface requires that the operation uses a region with a single argument to capture the expression. The region describes how to update the value of `x`. It takes the value at `x` as an input and must yield the updated value. Only the update to `x` is atomic. Generally the region must have only one instruction, but can potentially have more than one instructions too. The update is semantically similar to a compare-exchange loop based atomic update. }]; let cppNamespace = "::mlir::accomp"; let methods = [ InterfaceMethod<[{ Obtains `x` which is the address to which the value is atomically written to / read from. }], /*retTy=*/"::mlir::Value", /*methodName=*/"getX", /*args=*/(ins) >, InterfaceMethod<[{ Returns the first operation in atomic update region. }], /*retTy=*/"::mlir::Operation *", /*methodName=*/"getFirstOp", /*args=*/(ins), /*methodBody=*/"", /*defaultImplementation=*/[{ return &($_op.getRegion().front().getOperations().front()); }] >, InterfaceMethod<[{ Returns true if the new value is same as old value and the operation is a no-op, false otherwise. }], /*retTy=*/"bool", /*methodName=*/"isNoOp", /*args=*/(ins), /*methodBody=*/"", /*defaultImplementation=*/[{ // The atomic update is a no-op if the terminator is the first and only // operation in its region. mlir::Operation* terminator = llvm::dyn_cast($_op.getFirstOp()); return terminator && terminator->getOperands().front() == $_op.getRegion().front().getArgument(0); }] >, InterfaceMethod<[{ Returns the new value if the operation is equivalent to just a write operation. Otherwise, returns nullptr. }], /*retTy=*/"::mlir::Value", /*methodName=*/"getWriteOpVal", /*args=*/(ins), /*methodBody=*/"", /*defaultImplementation=*/[{ mlir::Operation* terminator = llvm::dyn_cast($_op.getFirstOp()); if (terminator && terminator->getOperands().front() != $_op.getRegion().front().getArgument(0)) { return terminator->getOperands().front(); } return nullptr; }] >, InterfaceMethod<[{ Common verifier for operation that implements atomic update interface. }], /*retTy=*/"::llvm::LogicalResult", /*methodName=*/"verifyCommon", /*args=*/(ins), /*methodBody=*/"", /*defaultImplementation=*/[{ if ($_op.getRegion().getNumArguments() != 1) return $_op.emitError("the region must accept exactly one argument"); Type elementType = $_op.getX().getType().getElementType(); if (elementType && elementType != $_op.getRegion().getArgument(0).getType()) { return $_op.emitError("the type of the operand must be a pointer type whose " "element type is the same as that of the region argument"); } return mlir::success(); }] >, InterfaceMethod<[{ Common verifier of the required region for operation that implements atomic update interface. }], /*retTy=*/"::llvm::LogicalResult", /*methodName=*/"verifyRegionsCommon", /*args=*/(ins), /*methodBody=*/"", /*defaultImplementation=*/[{ mlir::Operation *terminator = $_op.getRegion().front().getTerminator(); if (terminator->getOperands().size() != 1) return $_op.emitError("only updated value must be returned"); if (terminator->getOperands().front().getType() != $_op.getRegion().getArgument(0).getType()) return $_op.emitError("input and yielded value must have the same type"); return mlir::success(); }] >, ]; } def AtomicCaptureOpInterface : OpInterface<"AtomicCaptureOpInterface"> { let description = [{ This interface is used for OpenACC/OpenMP dialect operation that performs an atomic capture. This interface requires a single region with two operations that each implement one of the atomic interfaces. It can be found in one of these forms: `{ atomic.update, atomic.read }` `{ atomic.read, atomic.update }` `{ atomic.read, atomic.write }` }]; let cppNamespace = "::mlir::accomp"; let methods = [ InterfaceMethod<[{ Returns the first operation in atomic capture region. }], /*retTy=*/"::mlir::Operation *", /*methodName=*/"getFirstOp", /*args=*/(ins), /*methodBody=*/"", /*defaultImplementation=*/[{ return &($_op.getRegion().front().getOperations().front()); }] >, InterfaceMethod<[{ Returns the second operation in atomic capture region. }], /*retTy=*/"::mlir::Operation *", /*methodName=*/"getSecondOp", /*args=*/(ins), /*methodBody=*/"", /*defaultImplementation=*/[{ auto &ops = $_op.getRegion().front().getOperations(); return ops.getNextNode(ops.front()); }] >, InterfaceMethod<[{ Common verifier of the required region for operation that implements atomic capture interface. }], /*retTy=*/"::llvm::LogicalResult", /*methodName=*/"verifyRegionsCommon", /*args=*/(ins), /*methodBody=*/"", /*defaultImplementation=*/[{ Block::OpListType &ops = $_op.getRegion().front().getOperations(); if (ops.size() != 3) return $_op.emitError() << "expected three operations in atomic.capture region (one " "terminator, and two atomic ops)"; auto &firstOp = ops.front(); auto &secondOp = *ops.getNextNode(firstOp); auto firstReadStmt = dyn_cast(firstOp); auto firstUpdateStmt = dyn_cast(firstOp); auto secondReadStmt = dyn_cast(secondOp); auto secondUpdateStmt = dyn_cast(secondOp); auto secondWriteStmt = dyn_cast(secondOp); if (!((firstUpdateStmt && secondReadStmt) || (firstReadStmt && secondUpdateStmt) || (firstReadStmt && secondWriteStmt))) return ops.front().emitError() << "invalid sequence of operations in the capture region"; if (firstUpdateStmt && secondReadStmt && firstUpdateStmt.getX() != secondReadStmt.getX()) return firstUpdateStmt.emitError() << "updated variable in atomic.update must be captured in " "second operation"; if (firstReadStmt && secondUpdateStmt && firstReadStmt.getX() != secondUpdateStmt.getX()) return firstReadStmt.emitError() << "captured variable in atomic.read must be updated in second " "operation"; if (firstReadStmt && secondWriteStmt && firstReadStmt.getX() != secondWriteStmt.getX()) return firstReadStmt.emitError() << "captured variable in atomic.read must be updated in " "second operation"; return mlir::success(); }] >, ]; } def AtomicCompareOpInterface : OpInterface<"AtomicCompareOpInterface"> { let description = [{ This interface is used for OpenMP dialect operation that performs an atomic compare. The interface terminology uses `x`, `e`, and `d` like the directive specifications: `if (x == e) x = d` `x` is the address of the variable that is being compared and updated. The region describes the comparison and update logic. It takes the current value of `x` as a single block argument. The region has the following structure: ``` atomic.compare { ^bb0(%val_x): } ``` }]; let cppNamespace = "::mlir::accomp"; let methods = [ InterfaceMethod<[{ Obtains `x` which is the address of the variable that is being compared and potentially updated. }], /*retTy=*/"::mlir::Value", /*methodName=*/"getX", /*args=*/(ins) >, InterfaceMethod<[{ Returns the first operation in the atomic compare region. }], /*retTy=*/"::mlir::Operation *", /*methodName=*/"getFirstOp", /*args=*/(ins), /*methodBody=*/"", /*defaultImplementation=*/[{ return &($_op.getRegion().front().getOperations().front()); }] >, InterfaceMethod<[{ Common verifier for operation that implements atomic compare interface. }], /*retTy=*/"::llvm::LogicalResult", /*methodName=*/"verifyCommon", /*args=*/(ins), /*methodBody=*/"", /*defaultImplementation=*/[{ if ($_op.getRegion().getNumArguments() != 1) return $_op.emitError("the region must accept exactly one argument"); Type elementType = $_op.getX().getType().getElementType(); if (elementType && elementType != $_op.getRegion().getArgument(0).getType()) { return $_op.emitError("the type of the operand must be a pointer type whose " "element type is the same as that of the region argument"); } return mlir::success(); }] >, InterfaceMethod<[{ Common verifier of the required region for operation that implements atomic compare interface. }], /*retTy=*/"::llvm::LogicalResult", /*methodName=*/"verifyRegionsCommon", /*args=*/(ins), /*methodBody=*/"", /*defaultImplementation=*/[{ mlir::Region ®ion = $_op.getRegion(); if (region.empty()) return $_op.emitError( "region for atomic compare must not be empty"); mlir::Block &block = region.front(); if (block.empty()) return $_op.emitError( "region body for atomic compare must not be empty"); // The region must contain at least a comparison operation and a // terminator. A region with only a terminator is missing the // required comparison logic. if (block.getOperations().size() < 2) return $_op.emitError( "region must contain a comparison operation"); return mlir::success(); }] >, InterfaceMethod<[{ Common verifier for operator that implements atomic compare interface. Checks that the comparison operation in the region uses: 1) supported predicate for integer comparison : eq, slt, or sgt 2) supported predicate for float comparison : oeq, oglt or lsgt }], /*retTy=*/"::llvm::LogicalResult", /*methodName=*/"verifyOperator", /*args=*/(ins), /*methodBody=*/"", /*defaultImplementation=*/[{ mlir::Region ®ion = $_op.getRegion(); if (region.empty()) return $_op.emitError( "region for atomic compare must not be empty"); mlir::Block &block = region.front(); bool foundComparison = false; for (mlir::Operation &op : block.getOperations()) { llvm::StringRef opName = op.getName().getStringRef(); if (opName == "arith.cmpi" || opName == "llvm.icmp") { foundComparison = true; auto predAttr = op.getAttrOfType("predicate"); if (predAttr) { auto predName = mlir::arith::stringifyCmpIPredicate( static_cast(predAttr.getInt())); if (predName != "eq" && predName != "slt" && predName != "sgt") { return $_op.emitError( "unsupported comparison operator '") << predName << "' in atomic compare region, " "supported operators are: eq, slt, sgt"; } } break; } else if (opName == "arith.cmpf" || opName == "llvm.fcmp") { foundComparison = true; auto predAttr = op.getAttrOfType("predicate"); if (predAttr) { auto predName = mlir::arith::stringifyCmpFPredicate( static_cast(predAttr.getInt())); if (predName != "oeq" && predName != "ogt" && predName != "olt") { return $_op.emitError( "unsupported comparison operator '") << predName << "' in atomic compare region, " "supported operators are: oeq, ogt, olt"; } } break; } else if (opName == "fir.cmpc") { foundComparison = true; auto predAttr = op.getAttrOfType("predicate"); if (predAttr) { auto predName = mlir::arith::stringifyCmpFPredicate( static_cast(predAttr.getInt())); if (predName != "oeq") { return $_op.emitError( "unsupported comparison operator '") << predName << "' in atomic compare region for complex type, " "only 'oeq' is supported"; } } break; } } if (!foundComparison) return $_op.emitError( "atomic compare region must contain a comparison operation " "(arith.cmpi, arith.cmpf, llvm.icmp, llvm.fcmp, or fir.cmpc)"); return mlir::success(); }] >, ]; } #endif // OPENACC_MP_COMMON_INTERFACES_ATOMICINTERFACES