[Mlir-commits] [mlir] [OpenMP Dialect] Add omp.canonical_loop operation. (PR #65380)

llvmlistbot at llvm.org llvmlistbot at llvm.org
Tue Sep 5 09:45:14 PDT 2023


https://github.com/shraiysh created https://github.com/llvm/llvm-project/pull/65380:

This patch continues the work of D147658. It adds the `omp.canonical_loop` operation as the basic block for everything loop-related in OpenMP, such as worksharing-loop, distribute, loop transformation, etc.

In contrast to the current `omp.wsloop` approach
 * Loop-related semantics need to be implemented only once
 * Is composable with OpenMP loop transformations such as unrolling, tiling.
 * Is supposed to eventually support non-rectangular loops
 * Supports expressing non-perfectly nested loops

This patch only adds the MLIR representation; to something useful, I still have to implement lowering from Flang with at least the DO construct, and lowering to LLVM-IR using the OpenMPIRBuilder.

The pretty syntax currently is
```
omp.canonical_loop $iv in [0, %tripcount) { ... }
```
where `[0, %tripcount)` represents the half-open integer range of an OpenMP logical iteration space. Unbalanced parentheses/brackets and `0` keyword might not be universally liked. I could think of alternatives such as
```
omp.canonical_loop $iv = range(%tripcount) { ... }
```

Differential Revision: https://reviews.llvm.org/D155765


>From 9ba2822228327815bb8ce61dacebe6bc1c8b2270 Mon Sep 17 00:00:00 2001
From: Shraiysh Vaishay <shraiysh.vaishay at amd.com>
Date: Tue, 5 Sep 2023 11:29:28 -0500
Subject: [PATCH] [OpenMP Dialect] Add omp.canonical_loop operation.

This patch continues the work of D147658. It adds the `omp.canonical_loop` operation as the basic block for everything loop-related in OpenMP, such as worksharing-loop, distribute, loop transformation, etc.

In contrast to the current `omp.wsloop` approach
 * Loop-related semantics need to be implemented only once
 * Is composable with OpenMP loop transformations such as unrolling, tiling.
 * Is supposed to eventually support non-rectangular loops
 * Supports expressing non-perfectly nested loops

This patch only adds the MLIR representation; to something useful, I still have to implement lowering from Flang with at least the DO construct, and lowering to LLVM-IR using the OpenMPIRBuilder.

The pretty syntax currently is
```
omp.canonical_loop $iv in [0, %tripcount) { ... }
```
where `[0, %tripcount)` represents the half-open integer range of an OpenMP logical iteration space. Unbalanced parentheses/brackets and `0` keyword might not be universally liked. I could think of alternatives such as
```
omp.canonical_loop $iv = range(%tripcount) { ... }
```

Differential Revision: https://reviews.llvm.org/D155765
---
 .../mlir/Dialect/OpenMP/CMakeLists.txt        |   7 +-
 .../mlir/Dialect/OpenMP/OpenMPDialect.h       |   3 +
 mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td | 151 +++++++++++++++++-
 mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp  | 121 +++++++++++++-
 mlir/test/Dialect/OpenMP/cli.mlir             |  88 ++++++++++
 5 files changed, 363 insertions(+), 7 deletions(-)
 create mode 100644 mlir/test/Dialect/OpenMP/cli.mlir

diff --git a/mlir/include/mlir/Dialect/OpenMP/CMakeLists.txt b/mlir/include/mlir/Dialect/OpenMP/CMakeLists.txt
index 258b87d7471d314..d31d1f9ab21bb44 100644
--- a/mlir/include/mlir/Dialect/OpenMP/CMakeLists.txt
+++ b/mlir/include/mlir/Dialect/OpenMP/CMakeLists.txt
@@ -2,18 +2,15 @@ set(LLVM_TARGET_DEFINITIONS ${LLVM_MAIN_INCLUDE_DIR}/llvm/Frontend/OpenMP/OMP.td
 mlir_tablegen(OmpCommon.td --gen-directive-decl --directives-dialect=OpenMP)
 add_public_tablegen_target(omp_common_td)
 
+add_mlir_dialect(OpenMPOps omp)
 set(LLVM_TARGET_DEFINITIONS OpenMPOps.td)
-mlir_tablegen(OpenMPOpsDialect.h.inc -gen-dialect-decls -dialect=omp)
-mlir_tablegen(OpenMPOpsDialect.cpp.inc -gen-dialect-defs -dialect=omp)
-mlir_tablegen(OpenMPOps.h.inc -gen-op-decls)
-mlir_tablegen(OpenMPOps.cpp.inc -gen-op-defs)
 mlir_tablegen(OpenMPOpsEnums.h.inc -gen-enum-decls)
 mlir_tablegen(OpenMPOpsEnums.cpp.inc -gen-enum-defs)
 mlir_tablegen(OpenMPOpsAttributes.h.inc -gen-attrdef-decls -attrdefs-dialect=omp)
 mlir_tablegen(OpenMPOpsAttributes.cpp.inc -gen-attrdef-defs -attrdefs-dialect=omp)
 add_mlir_doc(OpenMPOps OpenMPDialect Dialects/ -gen-dialect-doc -dialect=omp)
-add_public_tablegen_target(MLIROpenMPOpsIncGen)
 add_dependencies(OpenMPDialectDocGen omp_common_td)
+
 add_mlir_interface(OpenMPOpsInterfaces)
 
 set(LLVM_TARGET_DEFINITIONS OpenMPTypeInterfaces.td)
diff --git a/mlir/include/mlir/Dialect/OpenMP/OpenMPDialect.h b/mlir/include/mlir/Dialect/OpenMP/OpenMPDialect.h
index 2f772a207d252a2..cb4c207eb1f06d6 100644
--- a/mlir/include/mlir/Dialect/OpenMP/OpenMPDialect.h
+++ b/mlir/include/mlir/Dialect/OpenMP/OpenMPDialect.h
@@ -25,6 +25,9 @@
 #include "mlir/Dialect/OpenMP/OpenMPOpsEnums.h.inc"
 #include "mlir/Dialect/OpenMP/OpenMPTypeInterfaces.h.inc"
 
+#define GET_TYPEDEF_CLASSES
+#include "mlir/Dialect/OpenMP/OpenMPOpsTypes.h.inc"
+
 #define GET_ATTRDEF_CLASSES
 #include "mlir/Dialect/OpenMP/OpenMPOpsAttributes.h.inc"
 
diff --git a/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td b/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td
index b1e1fe00b8594a7..44e5c08f859d958 100644
--- a/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td
+++ b/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td
@@ -28,6 +28,7 @@ def OpenMP_Dialect : Dialect {
   let cppNamespace = "::mlir::omp";
   let dependentDialects = ["::mlir::LLVM::LLVMDialect, ::mlir::func::FuncDialect"];
   let useDefaultAttributePrinterParser = 1;
+  let useDefaultTypePrinterParser = 1;
 }
 
 // OmpCommon requires definition of OpenACC_Dialect.
@@ -78,6 +79,10 @@ def TargetAttr : OpenMP_Attr<"Target", "target"> {
   let assemblyFormat = "`<` struct(params) `>`";
 }
 
+class OpenMP_Type<string name, string typeMnemonic> : 
+      TypeDef<OpenMP_Dialect, name> {
+  let mnemonic = typeMnemonic;
+}
 
 class OpenMP_Op<string mnemonic, list<Trait> traits = []> :
       Op<OpenMP_Dialect, mnemonic, traits>;
@@ -399,6 +404,150 @@ def SingleOp : OpenMP_Op<"single", [AttrSizedOperandSegments]> {
   let hasVerifier = 1;
 }
 
+//===---------------------------------------------------------------------===//
+// OpenMP Canonical Loop Operation					  	
+//===---------------------------------------------------------------------===//
+
+def CanonicalLoopInfoType : OpenMP_Type<"CanonicalLoopInfo", "cli"> {
+  let summary = "Type for representing a reference to a canonical loop";
+  let description = [{  
+    A variable of type CanonicalLoopInfo refers to an OpenMP-compatible
+    canonical loop in the same function. Variables of this type are not
+    available at runtime and therefore cannot be used by the program itself,
+    i.e. an opaque type. It is similar to the transform dialect's
+    `!transform.interface` type, but instead of implementing an interface
+    for each transformation, the OpenMP dialect itself defines possible
+    operations on this type.
+
+    A CanonicalLoopInfo variable can
+
+    1. be passed to omp.yield to be accessible outside the loop.
+    2. passed to omp operations that take a CanonicalLoopInfo argument,
+       such as `omp.unroll`.
+
+    A CanonicalLoopInfo variable can not
+
+    1. be returned from a function,
+    2. passed to operations that are not specifically designed to take a
+       CanonicalLoopInfo, including AnyType.
+
+    A CanonicalLoopInfo variable directly corresponds to an object of
+    OpenMPIRBuilder's CanonicalLoopInfo struct when lowering to LLVM-IR.
+  }];
+}
+
+def CanonicalLoopOp : OpenMP_Op<"canonical_loop", [SingleBlockImplicitTerminator<"omp::YieldOp">]> {
+  let summary = "OpenMP Canonical Loop Operation"; 
+  let description = [{
+    All loops that conform to OpenMP's definition of a canonical loop can be
+    simplified to a CanonicalLoopOp. In particular, there are no loop-carried
+    variables and the number of iterations it will execute is know before the
+    operation. This allows e.g. to determine the number of threads and chunks
+    the iterations space is split into before executing any iteration. More
+    restrictions may apply in cases such as (collapsed) loop nests, doacross
+    loops, etc.
+
+    The induction variable is always of the same type as the tripcount argument.
+    Since it can never be negative, tripcount is always interpreted as an
+    unsigned integer. It is the caller's responsbility to ensure the tripcount
+    is not negative when its interpretation is signed, i.e.
+    `%tripcount = max(0,%tripcount)`.
+
+    In contrast to other loop operations such as `scf.for`, the number of
+    iterations is determined by only a single variable, the trip-count. The
+    induction variable value is the logical iteration number of that iteration,
+    which OpenMP defines to be between 0 and the trip-count (exclusive).
+    Loop representation having lower-bound, upper-bound, and step-size operands,
+    require passes to do more work than necessary, including handling special
+    cases such as upper-bound smaller than lower-bound, upper-bound equal to
+    the integer type's maximal value, negative step size, etc. This complexity
+    is better only handled once by the front-end and can apply its semantics
+    for such cases while still being able to represent any kind of loop, which
+    kind of the point of a mid-end intermediate representation. User-defined
+    types such as random-access iterators in C++ could not directly be
+    represented anyway.
+
+    The return value of a omp.canonical_loop is a CanonicalLoopInfo that can be
+    used to refer to the canonical loop to apply transformations -- such as
+    tiling, unrolling, or work-sharing -- to the loop, similar to the transform
+    dialect but with OpenMP-specific semantics. To refer to nested canonical
+    loops, the CanonicalLoopInfo can be passed to omp.yield and becomes an
+    additional return value of the the outer `omp.canonical_loop`.
+    Every `omp.yield` on the loop body must be passed the same CanonicalLoopInfo
+    since nesting is a static/compile-time property.
+
+    A CanonicalLoopOp can be lowered to LLVM-IR using OpenMPIRBuilder's
+    createCanonicalLoop method.
+
+    #### Examples
+
+    Translation from lower-bound, upper-bount, step-size to trip-count.
+    ```c
+    for (int i = 3; i < 42; i+=2) {
+      B[i] = A[i];
+    }
+    ```
+
+    ```mlir
+    %lb = arith.constant 3 : i32
+    %ub = arith.constant 42 : i32
+    %step = arith.constant 2 : i32
+    %range = arith.sub %ub, %lb : i32
+    %tc = arith.div %range, %step : i32
+    %cli = omp.canonical_loop %iv : i32 in [0, %tc) {
+      %offset = arith.mul %iv, %step : i32
+      %i = arith.add %offset, %lb : i32
+      %a = load %arrA[%i] : memref<?xf32>
+      store %a, %arrB[%i] : memref<?xf32>
+    }
+    ```
+
+    Nested canonical loop with transformation.
+    ```mlir
+    %outer,%inner = omp.canonical_loop %iv1 : i32 in [0, %tripcount) {
+      %inner = omp.canonical_loop %iv2 : i32 in [0, %tc) {
+        %a = load %arrA[%iv1, %iv2] : memref<?x?xf32>
+        store %a, %arrB[%iv1, %iv2] : memref<?x?xf32>
+      }
+      omp.yield(%inner : !omp.cli)
+    }
+    omp.tile(%outer, %inner : !omp.cli, !omp.cli)
+    ```
+
+    Nested canonical loop with other constructs. The `omp.distribute`
+    operation has not been added yet, so this is suggested use with other
+    constructs.
+    ```mlir
+    omp.target {
+      %outer,%inner = omp.canonical_loop %iv1 : i32 in [0, %tripcount) {
+        %inner = omp.canonical_loop %iv2 : i32 in [0, %tc) {
+          %a = load %arrA[%iv1, %iv2] : memref<?x?xf32>
+          store %a, %arrB[%iv1, %iv2] : memref<?x?xf32>
+        }
+        omp.yield(%inner : !omp.cli)
+      }
+      %collapsed_loopinfo = omp.collapse(%outer, %inner)
+      omp.teams {
+        call @foo() : () -> ()
+        omp.distribute(%collapsed_loopinfo)
+      }
+    }
+    ```
+
+  }];
+  let hasCustomAssemblyFormat = 1;
+  let hasVerifier = 1;
+
+  let arguments = (ins IntLikeType:$tripCount);
+  let regions = (region AnyRegion:$region);
+  let results = (outs Variadic<CanonicalLoopInfoType>:$loopInfo);
+
+  let extraClassDeclaration = [{
+    ::mlir::Value getInductionVar();
+  }];
+}
+
+
 //===----------------------------------------------------------------------===//
 // 2.9.2 Workshare Loop Construct
 //===----------------------------------------------------------------------===//
@@ -613,7 +762,7 @@ def SimdLoopOp : OpenMP_Op<"simdloop", [AttrSizedOperandSegments,
 def YieldOp : OpenMP_Op<"yield",
     [Pure, ReturnLike, Terminator,
      ParentOneOf<["WsLoopOp", "ReductionDeclareOp",
-     "AtomicUpdateOp", "SimdLoopOp"]>]> {
+     "AtomicUpdateOp", "SimdLoopOp", "CanonicalLoopOp"]>]> {
   let summary = "loop yield and termination operation";
   let description = [{
     "omp.yield" yields SSA values from the OpenMP dialect op region and
diff --git a/mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp b/mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp
index 2ba5f1aca9cf6b2..76440e7f71484b3 100644
--- a/mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp
+++ b/mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp
@@ -1,4 +1,4 @@
-//===- OpenMPDialect.cpp - MLIR Dialect for OpenMP implementation ---------===//
+//===- OpenMPDialect.cpp - MLIR Dialect for OpenMP implementation ---------===//
 //
 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
 // See https://llvm.org/LICENSE.txt for license information.
@@ -65,6 +65,10 @@ void OpenMPDialect::initialize() {
 #define GET_ATTRDEF_LIST
 #include "mlir/Dialect/OpenMP/OpenMPOpsAttributes.cpp.inc"
       >();
+  addTypes<
+#define GET_TYPEDEF_LIST
+#include "mlir/Dialect/OpenMP/OpenMPOpsTypes.cpp.inc"
+      >();
 
   addInterface<OpenMPDialectFoldInterface>();
   LLVM::LLVMPointerType::attachInterface<
@@ -1524,8 +1528,123 @@ LogicalResult CancellationPointOp::verify() {
   return success();
 }
 
+//===----------------------------------------------------------------------===//
+// CanonicaLoopOp
+//===----------------------------------------------------------------------===//
+
+Value mlir::omp::CanonicalLoopOp::getInductionVar() {
+  return getRegion().getArgument(0);
+}
+
+void mlir::omp::CanonicalLoopOp::print(OpAsmPrinter &p) {
+  p << " " << getInductionVar() << " : " << getInductionVar().getType()
+    << " in [0, " << getTripCount() << ") ";
+
+  // omp.yield is implicit if no arguments passed to it.
+  p.printRegion(getRegion(), /*printEntryBlockArgs=*/false,
+                /*printBlockTerminators=*/getResultTypes().size() > 1);
+
+  p.printOptionalAttrDict((*this)->getAttrs());
+}
+
+mlir::ParseResult
+mlir::omp::CanonicalLoopOp::parse(::mlir::OpAsmParser &parser,
+                                  ::mlir::OperationState &result) {
+  Builder &builder = parser.getBuilder();
+  MLIRContext *context = parser.getContext();
+
+  // We derive the type of tripCount from inductionVariable. Unfortunatelty we
+  // cannot do the other way around because MLIR requires the type of tripCount
+  // to be known when calling resolveOperand.
+  OpAsmParser::Argument inductionVariable;
+  if (parser.parseArgument(inductionVariable, /*allowType*/ true) ||
+      parser.parseKeyword("in") || parser.parseLSquare())
+    return failure();
+
+  int zero = -1;
+  SMLoc zeroLoc = parser.getCurrentLocation();
+  if (parser.parseInteger(zero))
+    return failure();
+  if (zero != 0) {
+    parser.emitError(zeroLoc, "Logical iteration space starts with zero");
+    return failure();
+  }
+
+  OpAsmParser::UnresolvedOperand tripcount;
+  if (parser.parseComma() || parser.parseOperand(tripcount) ||
+      parser.parseRParen() ||
+      parser.resolveOperand(tripcount, inductionVariable.type, result.operands))
+    return failure();
+
+  // Parse the loop body.
+  Region *region = result.addRegion();
+  if (parser.parseRegion(*region, {inductionVariable}))
+    return failure();
+  CanonicalLoopOp::ensureTerminator(*region, builder, result.location);
+
+  // Return the CanonicalLoopInfo for this loop, plus the CanonicalLoopInfos
+  // passed to omp.yield.
+  int numResults = 0;
+  for (Block &block : *region) {
+    if (auto yield = dyn_cast<YieldOp>(block.getTerminator())) {
+      numResults = yield.getNumOperands();
+      break;
+    }
+  }
+  numResults += 1;
+  for (int i = 0; i < numResults; ++i)
+    result.types.push_back(CanonicalLoopInfoType::get(context));
+
+  // Parse the optional attribute list.
+  if (parser.parseOptionalAttrDict(result.attributes))
+    return failure();
+
+  return mlir::success();
+}
+
+LogicalResult CanonicalLoopOp::verify() {
+  Value indVar = getInductionVar();
+  Value tripCount = getTripCount();
+  Block *body = getBody();
+  Region &region = getRegion();
+
+  if (indVar.getType() != tripCount.getType())
+    return emitOpError(
+        "Region argument must be the same type as the trip count");
+
+  auto numResults = getResultTypes().size();
+  if (numResults <= 0)
+    return emitOpError(
+        "omp.canonical_loop must return at least one CanonicalLoopInfo");
+
+  // Check arguments to omp.yield operations
+  YieldOp *firstYield = nullptr;
+  for (Block &block : region) {
+    if (auto yield = dyn_cast<YieldOp>(block.getTerminator())) {
+      if (yield.getNumOperands() != numResults - 1)
+        return emitOpError("omp.yield arguments must match number of return "
+                           "CanonicalLoopInfo's");
+
+      if (!firstYield) {
+        firstYield = &yield;
+        continue;
+      }
+
+      for (int i = 0; i < numResults - 1; ++i) {
+        if (yield.getOperand(i) != firstYield->getOperand(i))
+          return emitOpError("Each omp.yield must return the same values");
+      }
+    }
+  }
+
+  return success();
+}
+
 #define GET_ATTRDEF_CLASSES
 #include "mlir/Dialect/OpenMP/OpenMPOpsAttributes.cpp.inc"
 
 #define GET_OP_CLASSES
 #include "mlir/Dialect/OpenMP/OpenMPOps.cpp.inc"
+
+#define GET_TYPEDEF_CLASSES
+#include "mlir/Dialect/OpenMP/OpenMPOpsTypes.cpp.inc"
diff --git a/mlir/test/Dialect/OpenMP/cli.mlir b/mlir/test/Dialect/OpenMP/cli.mlir
new file mode 100644
index 000000000000000..db9d9ed4dba6132
--- /dev/null
+++ b/mlir/test/Dialect/OpenMP/cli.mlir
@@ -0,0 +1,88 @@
+// RUN: mlir-opt %s | mlir-opt | FileCheck %s
+
+// CHECK-LABEL: @omp_canonloop_raw
+// CHECK-SAME: (%[[tc:.*]]: i32)
+func.func @omp_canonloop_raw(%tc : i32) -> () {
+  // CHECK: %{{.*}} = omp.canonical_loop %{{.*}} : i32 in [0, %[[tc]]) {
+  %cli = "omp.canonical_loop" (%tc) ({
+    ^bb0(%iv: i32):
+      // omp.yield without argument is implicit
+      // CHECK-NOT: omp.yield
+      omp.yield
+  }) : (i32) -> (!omp.cli)
+  return
+}
+
+// CHECK-LABEL: @omp_nested_canonloop_raw
+// CHECK-SAME: (%[[tc_outer:.*]]: i32, %[[tc_inner:.*]]: i32)
+func.func @omp_nested_canonloop_raw(%tc_outer : i32, %tc_inner : i32) -> () {
+  // CHECK: %{{.*}} = omp.canonical_loop %{{.*}} : i32 in [0, %[[tc_outer]]) {
+  %outer,%inner = "omp.canonical_loop" (%tc_outer) ({
+    ^bb_outer(%iv_outer: i32):
+      // CHECK: %[[inner_cli:.*]] = omp.canonical_loop %{{.*}} : i32 in [0, %[[tc_inner]]) {
+      %inner = "omp.canonical_loop" (%tc_inner) ({
+        ^bb_inner(%iv_inner: i32):
+          omp.yield
+      }) : (i32) -> (!omp.cli)
+      // CHECK: omp.yield(%[[inner_cli]] : !omp.cli)
+      omp.yield (%inner : !omp.cli)
+  }) : (i32) -> (!omp.cli, !omp.cli)
+  return
+}
+
+// CHECK-LABEL: @omp_triple_nested_canonloop_raw
+func.func @omp_triple_nested_canonloop_raw(%tc_outer : i32,%tc_middle : i32, %tc_inner : i32) -> () {
+  // CHECK: %{{.*}} = omp.canonical_loop %{{.*}} : i32 in [0, %{{.*}}) {
+  %outer, %middle, %inner = "omp.canonical_loop" (%tc_outer) ({
+    ^bb_outer(%iv_outer: i32):
+      // CHECK: %[[middle:.*]]:2 = omp.canonical_loop %{{.*}} : i32 in [0, %{{.*}}) {
+      %middle, %inner= "omp.canonical_loop" (%tc_middle) ({
+        ^bb_middle(%iv_middle: i32):
+          // CHECK: %[[inner:.*]] = omp.canonical_loop %{{.*}} : i32 in [0, %{{.*}}) {
+          %inner = "omp.canonical_loop" (%tc_inner) ({
+            ^bb_inner(%iv_inner: i32):
+              omp.yield
+          }) : (i32) -> (!omp.cli)
+        // CHECK: omp.yield(%[[inner]] : !omp.cli)
+        omp.yield (%inner : !omp.cli)
+    }) : (i32) -> (!omp.cli,!omp.cli)
+    // CHECK: omp.yield(%[[middle]]#0, %[[middle]]#1 : !omp.cli, !omp.cli)
+    omp.yield (%middle, %inner : !omp.cli, !omp.cli)
+  }) : (i32) -> (!omp.cli, !omp.cli, !omp.cli)
+  return
+}
+
+// CHECK-LABEL: @omp_canonloop_pretty
+// CHECK-SAME: (%[[tc:.*]]: i32)
+func.func @omp_canonloop_pretty(%tc : i32) -> () {
+  // CHECK: %{{.*}} = omp.canonical_loop %[[iv:.*]] : i32 in [0, %[[tc]]) {
+  %cli = omp.canonical_loop %iv : i32 in [0, %tc) {
+    // CHECK-NEXT: %{{.*}} = llvm.add %[[iv]], %[[iv]] : i32
+    %newval = llvm.add %iv, %iv: i32
+    // CHECK-NOT: omp.yield
+  }
+  return
+}
+
+// CHECK-LABEL: @omp_canonloop_implicit_yield
+func.func @omp_canonloop_implicit_yield(%tc : i32) -> () {
+  // CHECK: %{{.*}} = omp.canonical_loop %{{.*}} : i32 in [0, %{{.*}}) {
+  %cli = omp.canonical_loop %iv : i32 in [0, %tc) {
+    // CHECK-NOT: omp.yield
+    // CHECK-NEXT: }
+  }
+  return
+}
+
+// CHECK-LABEL: @omp_canonloop_nested_pretty
+func.func @omp_canonloop_nested_pretty(%tc : i32) -> () {
+  // CHECK: %{{.*}} = omp.canonical_loop %{{.*}} : i32 in [0, %{{.*}}) {
+  %outer,%inner = omp.canonical_loop %iv1 : i32 in [0, %tc) {
+    // CHECK: %[[inner:.*]] = omp.canonical_loop %{{.*}} : i32 in [0, %{{.*}}) {
+    %inner = omp.canonical_loop %iv2 : i32 in [0, %tc) {}
+    // CHECK:  omp.yield(%[[inner]] : !omp.cli)
+    omp.yield (%inner : !omp.cli)
+  }
+  return
+}
+



More information about the Mlir-commits mailing list