Skip to content

Commit 88fc39d

Browse files
committed
[mlir][OpenMP] Convert omp.cancellation_point to LLVMIR
This is basically identical to cancel except without the if clause. taskgroup will be implemented in a followup PR.
1 parent bb374c9 commit 88fc39d

File tree

5 files changed

+293
-9
lines changed

5 files changed

+293
-9
lines changed

llvm/include/llvm/Frontend/OpenMP/OMPIRBuilder.h

+10
Original file line numberDiff line numberDiff line change
@@ -686,6 +686,16 @@ class OpenMPIRBuilder {
686686
Value *IfCondition,
687687
omp::Directive CanceledDirective);
688688

689+
/// Generator for '#omp cancellation point'
690+
///
691+
/// \param Loc The location where the directive was encountered.
692+
/// \param CanceledDirective The kind of directive that is cancled.
693+
///
694+
/// \returns The insertion point after the barrier.
695+
InsertPointOrErrorTy
696+
createCancellationPoint(const LocationDescription &Loc,
697+
omp::Directive CanceledDirective);
698+
689699
/// Generator for '#omp parallel'
690700
///
691701
/// \param Loc The insert and source location description.

llvm/lib/Frontend/OpenMP/OMPIRBuilder.cpp

+51
Original file line numberDiff line numberDiff line change
@@ -1118,6 +1118,57 @@ OpenMPIRBuilder::createCancel(const LocationDescription &Loc,
11181118
return Builder.saveIP();
11191119
}
11201120

1121+
OpenMPIRBuilder::InsertPointOrErrorTy
1122+
OpenMPIRBuilder::createCancellationPoint(const LocationDescription &Loc,
1123+
omp::Directive CanceledDirective) {
1124+
if (!updateToLocation(Loc))
1125+
return Loc.IP;
1126+
1127+
// LLVM utilities like blocks with terminators.
1128+
auto *UI = Builder.CreateUnreachable();
1129+
Builder.SetInsertPoint(UI);
1130+
1131+
Value *CancelKind = nullptr;
1132+
switch (CanceledDirective) {
1133+
#define OMP_CANCEL_KIND(Enum, Str, DirectiveEnum, Value) \
1134+
case DirectiveEnum: \
1135+
CancelKind = Builder.getInt32(Value); \
1136+
break;
1137+
#include "llvm/Frontend/OpenMP/OMPKinds.def"
1138+
default:
1139+
llvm_unreachable("Unknown cancel kind!");
1140+
}
1141+
1142+
uint32_t SrcLocStrSize;
1143+
Constant *SrcLocStr = getOrCreateSrcLocStr(Loc, SrcLocStrSize);
1144+
Value *Ident = getOrCreateIdent(SrcLocStr, SrcLocStrSize);
1145+
Value *Args[] = {Ident, getOrCreateThreadID(Ident), CancelKind};
1146+
Value *Result = Builder.CreateCall(
1147+
getOrCreateRuntimeFunctionPtr(OMPRTL___kmpc_cancellationpoint), Args);
1148+
auto ExitCB = [this, CanceledDirective, Loc](InsertPointTy IP) -> Error {
1149+
if (CanceledDirective == OMPD_parallel) {
1150+
IRBuilder<>::InsertPointGuard IPG(Builder);
1151+
Builder.restoreIP(IP);
1152+
return createBarrier(LocationDescription(Builder.saveIP(), Loc.DL),
1153+
omp::Directive::OMPD_unknown,
1154+
/* ForceSimpleCall */ false,
1155+
/* CheckCancelFlag */ false)
1156+
.takeError();
1157+
}
1158+
return Error::success();
1159+
};
1160+
1161+
// The actual cancel logic is shared with others, e.g., cancel_barriers.
1162+
if (Error Err = emitCancelationCheckImpl(Result, CanceledDirective, ExitCB))
1163+
return Err;
1164+
1165+
// Update the insertion point and remove the terminator we introduced.
1166+
Builder.SetInsertPoint(UI->getParent());
1167+
UI->eraseFromParent();
1168+
1169+
return Builder.saveIP();
1170+
}
1171+
11211172
OpenMPIRBuilder::InsertPointTy OpenMPIRBuilder::emitTargetKernel(
11221173
const LocationDescription &Loc, InsertPointTy AllocaIP, Value *&Return,
11231174
Value *Ident, Value *DeviceID, Value *NumTeams, Value *NumThreads,

mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp

+34-3
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,9 @@ static LogicalResult checkImplementationStatus(Operation &op) {
255255
LogicalResult result = success();
256256
llvm::TypeSwitch<Operation &>(op)
257257
.Case([&](omp::CancelOp op) { checkCancelDirective(op, result); })
258+
.Case([&](omp::CancellationPointOp op) {
259+
checkCancelDirective(op, result);
260+
})
258261
.Case([&](omp::DistributeOp op) {
259262
checkAllocate(op, result);
260263
checkDistSchedule(op, result);
@@ -1589,11 +1592,12 @@ cleanupPrivateVars(llvm::IRBuilderBase &builder,
15891592

15901593
/// Returns true if the construct contains omp.cancel or omp.cancellation_point
15911594
static bool constructIsCancellable(Operation *op) {
1592-
// omp.cancel must be "closely nested" so it will be visible and not inside of
1593-
// funcion calls. This is enforced by the verifier.
1595+
// omp.cancel and omp.cancellation_point must be "closely nested" so they will
1596+
// be visible and not inside of funcion calls. This is enforced by the
1597+
// verifier.
15941598
bool containsCancel = false;
15951599
op->walk([&containsCancel](Operation *child) {
1596-
if (mlir::isa<omp::CancelOp>(child)) {
1600+
if (mlir::isa<omp::CancelOp, omp::CancellationPointOp>(child)) {
15971601
containsCancel = true;
15981602
return WalkResult::interrupt();
15991603
}
@@ -3091,6 +3095,30 @@ convertOmpCancel(omp::CancelOp op, llvm::IRBuilderBase &builder,
30913095
return success();
30923096
}
30933097

3098+
static LogicalResult
3099+
convertOmpCancellationPoint(omp::CancellationPointOp op,
3100+
llvm::IRBuilderBase &builder,
3101+
LLVM::ModuleTranslation &moduleTranslation) {
3102+
llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
3103+
llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
3104+
3105+
if (failed(checkImplementationStatus(*op.getOperation())))
3106+
return failure();
3107+
3108+
llvm::omp::Directive cancelledDirective =
3109+
convertCancellationConstructType(op.getCancelDirective());
3110+
3111+
llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
3112+
ompBuilder->createCancellationPoint(ompLoc, cancelledDirective);
3113+
3114+
if (failed(handleError(afterIP, *op.getOperation())))
3115+
return failure();
3116+
3117+
builder.restoreIP(afterIP.get());
3118+
3119+
return success();
3120+
}
3121+
30943122
/// Converts an OpenMP Threadprivate operation into LLVM IR using
30953123
/// OpenMPIRBuilder.
30963124
static LogicalResult
@@ -5524,6 +5552,9 @@ convertHostOrTargetOperation(Operation *op, llvm::IRBuilderBase &builder,
55245552
.Case([&](omp::CancelOp op) {
55255553
return convertOmpCancel(op, builder, moduleTranslation);
55265554
})
5555+
.Case([&](omp::CancellationPointOp op) {
5556+
return convertOmpCancellationPoint(op, builder, moduleTranslation);
5557+
})
55275558
.Case([&](omp::SectionsOp) {
55285559
return convertOmpSections(*op, builder, moduleTranslation);
55295560
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
// RUN: mlir-translate --mlir-to-llvmir %s | FileCheck %s
2+
3+
llvm.func @cancellation_point_parallel() {
4+
omp.parallel {
5+
omp.cancellation_point cancellation_construct_type(parallel)
6+
omp.terminator
7+
}
8+
llvm.return
9+
}
10+
// CHECK-LABEL: define internal void @cancellation_point_parallel..omp_par
11+
// CHECK: omp.par.entry:
12+
// CHECK: %[[VAL_5:.*]] = alloca i32, align 4
13+
// CHECK: %[[VAL_6:.*]] = load i32, ptr %[[VAL_7:.*]], align 4
14+
// CHECK: store i32 %[[VAL_6]], ptr %[[VAL_5]], align 4
15+
// CHECK: %[[VAL_8:.*]] = load i32, ptr %[[VAL_5]], align 4
16+
// CHECK: br label %[[VAL_9:.*]]
17+
// CHECK: omp.region.after_alloca: ; preds = %[[VAL_10:.*]]
18+
// CHECK: br label %[[VAL_11:.*]]
19+
// CHECK: omp.par.region: ; preds = %[[VAL_9]]
20+
// CHECK: br label %[[VAL_12:.*]]
21+
// CHECK: omp.par.region1: ; preds = %[[VAL_11]]
22+
// CHECK: %[[VAL_13:.*]] = call i32 @__kmpc_global_thread_num(ptr @1)
23+
// CHECK: %[[VAL_14:.*]] = call i32 @__kmpc_cancellationpoint(ptr @1, i32 %[[VAL_13]], i32 1)
24+
// CHECK: %[[VAL_15:.*]] = icmp eq i32 %[[VAL_14]], 0
25+
// CHECK: br i1 %[[VAL_15]], label %[[VAL_16:.*]], label %[[VAL_17:.*]]
26+
// CHECK: omp.par.region1.cncl: ; preds = %[[VAL_12]]
27+
// CHECK: %[[VAL_18:.*]] = call i32 @__kmpc_global_thread_num(ptr @1)
28+
// CHECK: %[[VAL_19:.*]] = call i32 @__kmpc_cancel_barrier(ptr @2, i32 %[[VAL_18]])
29+
// CHECK: br label %[[VAL_20:.*]]
30+
// CHECK: omp.par.region1.split: ; preds = %[[VAL_12]]
31+
// CHECK: br label %[[VAL_21:.*]]
32+
// CHECK: omp.region.cont: ; preds = %[[VAL_16]]
33+
// CHECK: br label %[[VAL_22:.*]]
34+
// CHECK: omp.par.pre_finalize: ; preds = %[[VAL_21]]
35+
// CHECK: br label %[[VAL_20]]
36+
// CHECK: omp.par.exit.exitStub: ; preds = %[[VAL_22]], %[[VAL_17]]
37+
// CHECK: ret void
38+
39+
llvm.func @cancellation_point_sections() {
40+
omp.sections {
41+
omp.section {
42+
omp.cancellation_point cancellation_construct_type(sections)
43+
omp.terminator
44+
}
45+
omp.terminator
46+
}
47+
llvm.return
48+
}
49+
// CHECK-LABEL: define void @cancellation_point_sections
50+
// CHECK: %[[VAL_23:.*]] = alloca i32, align 4
51+
// CHECK: %[[VAL_24:.*]] = alloca i32, align 4
52+
// CHECK: %[[VAL_25:.*]] = alloca i32, align 4
53+
// CHECK: %[[VAL_26:.*]] = alloca i32, align 4
54+
// CHECK: br label %[[VAL_27:.*]]
55+
// CHECK: entry: ; preds = %[[VAL_28:.*]]
56+
// CHECK: br label %[[VAL_29:.*]]
57+
// CHECK: omp_section_loop.preheader: ; preds = %[[VAL_27]]
58+
// CHECK: store i32 0, ptr %[[VAL_24]], align 4
59+
// CHECK: store i32 0, ptr %[[VAL_25]], align 4
60+
// CHECK: store i32 1, ptr %[[VAL_26]], align 4
61+
// CHECK: %[[VAL_30:.*]] = call i32 @__kmpc_global_thread_num(ptr @1)
62+
// CHECK: call void @__kmpc_for_static_init_4u(ptr @1, i32 %[[VAL_30]], i32 34, ptr %[[VAL_23]], ptr %[[VAL_24]], ptr %[[VAL_25]], ptr %[[VAL_26]], i32 1, i32 0)
63+
// CHECK: %[[VAL_31:.*]] = load i32, ptr %[[VAL_24]], align 4
64+
// CHECK: %[[VAL_32:.*]] = load i32, ptr %[[VAL_25]], align 4
65+
// CHECK: %[[VAL_33:.*]] = sub i32 %[[VAL_32]], %[[VAL_31]]
66+
// CHECK: %[[VAL_34:.*]] = add i32 %[[VAL_33]], 1
67+
// CHECK: br label %[[VAL_35:.*]]
68+
// CHECK: omp_section_loop.header: ; preds = %[[VAL_36:.*]], %[[VAL_29]]
69+
// CHECK: %[[VAL_37:.*]] = phi i32 [ 0, %[[VAL_29]] ], [ %[[VAL_38:.*]], %[[VAL_36]] ]
70+
// CHECK: br label %[[VAL_39:.*]]
71+
// CHECK: omp_section_loop.cond: ; preds = %[[VAL_35]]
72+
// CHECK: %[[VAL_40:.*]] = icmp ult i32 %[[VAL_37]], %[[VAL_34]]
73+
// CHECK: br i1 %[[VAL_40]], label %[[VAL_41:.*]], label %[[VAL_42:.*]]
74+
// CHECK: omp_section_loop.body: ; preds = %[[VAL_39]]
75+
// CHECK: %[[VAL_43:.*]] = add i32 %[[VAL_37]], %[[VAL_31]]
76+
// CHECK: %[[VAL_44:.*]] = mul i32 %[[VAL_43]], 1
77+
// CHECK: %[[VAL_45:.*]] = add i32 %[[VAL_44]], 0
78+
// CHECK: switch i32 %[[VAL_45]], label %[[VAL_46:.*]] [
79+
// CHECK: i32 0, label %[[VAL_47:.*]]
80+
// CHECK: ]
81+
// CHECK: omp_section_loop.body.case: ; preds = %[[VAL_41]]
82+
// CHECK: br label %[[VAL_48:.*]]
83+
// CHECK: omp.section.region: ; preds = %[[VAL_47]]
84+
// CHECK: %[[VAL_49:.*]] = call i32 @__kmpc_global_thread_num(ptr @1)
85+
// CHECK: %[[VAL_50:.*]] = call i32 @__kmpc_cancellationpoint(ptr @1, i32 %[[VAL_49]], i32 3)
86+
// CHECK: %[[VAL_51:.*]] = icmp eq i32 %[[VAL_50]], 0
87+
// CHECK: br i1 %[[VAL_51]], label %[[VAL_52:.*]], label %[[VAL_53:.*]]
88+
// CHECK: omp.section.region.split: ; preds = %[[VAL_48]]
89+
// CHECK: br label %[[VAL_54:.*]]
90+
// CHECK: omp.region.cont: ; preds = %[[VAL_52]]
91+
// CHECK: br label %[[VAL_46]]
92+
// CHECK: omp_section_loop.body.sections.after: ; preds = %[[VAL_54]], %[[VAL_41]]
93+
// CHECK: br label %[[VAL_36]]
94+
// CHECK: omp_section_loop.inc: ; preds = %[[VAL_46]]
95+
// CHECK: %[[VAL_38]] = add nuw i32 %[[VAL_37]], 1
96+
// CHECK: br label %[[VAL_35]]
97+
// CHECK: omp_section_loop.exit: ; preds = %[[VAL_53]], %[[VAL_39]]
98+
// CHECK: call void @__kmpc_for_static_fini(ptr @1, i32 %[[VAL_30]])
99+
// CHECK: %[[VAL_55:.*]] = call i32 @__kmpc_global_thread_num(ptr @1)
100+
// CHECK: call void @__kmpc_barrier(ptr @2, i32 %[[VAL_55]])
101+
// CHECK: br label %[[VAL_56:.*]]
102+
// CHECK: omp_section_loop.after: ; preds = %[[VAL_42]]
103+
// CHECK: br label %[[VAL_57:.*]]
104+
// CHECK: omp_section_loop.aftersections.fini: ; preds = %[[VAL_56]]
105+
// CHECK: ret void
106+
// CHECK: omp.section.region.cncl: ; preds = %[[VAL_48]]
107+
// CHECK: br label %[[VAL_42]]
108+
109+
llvm.func @cancellation_point_wsloop(%lb : i32, %ub : i32, %step : i32) {
110+
omp.wsloop {
111+
omp.loop_nest (%iv) : i32 = (%lb) to (%ub) step (%step) {
112+
omp.cancellation_point cancellation_construct_type(loop)
113+
omp.yield
114+
}
115+
}
116+
llvm.return
117+
}
118+
// CHECK-LABEL: define void @cancellation_point_wsloop
119+
// CHECK: %[[VAL_58:.*]] = alloca i32, align 4
120+
// CHECK: %[[VAL_59:.*]] = alloca i32, align 4
121+
// CHECK: %[[VAL_60:.*]] = alloca i32, align 4
122+
// CHECK: %[[VAL_61:.*]] = alloca i32, align 4
123+
// CHECK: br label %[[VAL_62:.*]]
124+
// CHECK: omp.region.after_alloca: ; preds = %[[VAL_63:.*]]
125+
// CHECK: br label %[[VAL_64:.*]]
126+
// CHECK: entry: ; preds = %[[VAL_62]]
127+
// CHECK: br label %[[VAL_65:.*]]
128+
// CHECK: omp.wsloop.region: ; preds = %[[VAL_64]]
129+
// CHECK: %[[VAL_66:.*]] = icmp slt i32 %[[VAL_67:.*]], 0
130+
// CHECK: %[[VAL_68:.*]] = sub i32 0, %[[VAL_67]]
131+
// CHECK: %[[VAL_69:.*]] = select i1 %[[VAL_66]], i32 %[[VAL_68]], i32 %[[VAL_67]]
132+
// CHECK: %[[VAL_70:.*]] = select i1 %[[VAL_66]], i32 %[[VAL_71:.*]], i32 %[[VAL_72:.*]]
133+
// CHECK: %[[VAL_73:.*]] = select i1 %[[VAL_66]], i32 %[[VAL_72]], i32 %[[VAL_71]]
134+
// CHECK: %[[VAL_74:.*]] = sub nsw i32 %[[VAL_73]], %[[VAL_70]]
135+
// CHECK: %[[VAL_75:.*]] = icmp sle i32 %[[VAL_73]], %[[VAL_70]]
136+
// CHECK: %[[VAL_76:.*]] = sub i32 %[[VAL_74]], 1
137+
// CHECK: %[[VAL_77:.*]] = udiv i32 %[[VAL_76]], %[[VAL_69]]
138+
// CHECK: %[[VAL_78:.*]] = add i32 %[[VAL_77]], 1
139+
// CHECK: %[[VAL_79:.*]] = icmp ule i32 %[[VAL_74]], %[[VAL_69]]
140+
// CHECK: %[[VAL_80:.*]] = select i1 %[[VAL_79]], i32 1, i32 %[[VAL_78]]
141+
// CHECK: %[[VAL_81:.*]] = select i1 %[[VAL_75]], i32 0, i32 %[[VAL_80]]
142+
// CHECK: br label %[[VAL_82:.*]]
143+
// CHECK: omp_loop.preheader: ; preds = %[[VAL_65]]
144+
// CHECK: store i32 0, ptr %[[VAL_59]], align 4
145+
// CHECK: %[[VAL_83:.*]] = sub i32 %[[VAL_81]], 1
146+
// CHECK: store i32 %[[VAL_83]], ptr %[[VAL_60]], align 4
147+
// CHECK: store i32 1, ptr %[[VAL_61]], align 4
148+
// CHECK: %[[VAL_84:.*]] = call i32 @__kmpc_global_thread_num(ptr @1)
149+
// CHECK: call void @__kmpc_for_static_init_4u(ptr @1, i32 %[[VAL_84]], i32 34, ptr %[[VAL_58]], ptr %[[VAL_59]], ptr %[[VAL_60]], ptr %[[VAL_61]], i32 1, i32 0)
150+
// CHECK: %[[VAL_85:.*]] = load i32, ptr %[[VAL_59]], align 4
151+
// CHECK: %[[VAL_86:.*]] = load i32, ptr %[[VAL_60]], align 4
152+
// CHECK: %[[VAL_87:.*]] = sub i32 %[[VAL_86]], %[[VAL_85]]
153+
// CHECK: %[[VAL_88:.*]] = add i32 %[[VAL_87]], 1
154+
// CHECK: br label %[[VAL_89:.*]]
155+
// CHECK: omp_loop.header: ; preds = %[[VAL_90:.*]], %[[VAL_82]]
156+
// CHECK: %[[VAL_91:.*]] = phi i32 [ 0, %[[VAL_82]] ], [ %[[VAL_92:.*]], %[[VAL_90]] ]
157+
// CHECK: br label %[[VAL_93:.*]]
158+
// CHECK: omp_loop.cond: ; preds = %[[VAL_89]]
159+
// CHECK: %[[VAL_94:.*]] = icmp ult i32 %[[VAL_91]], %[[VAL_88]]
160+
// CHECK: br i1 %[[VAL_94]], label %[[VAL_95:.*]], label %[[VAL_96:.*]]
161+
// CHECK: omp_loop.body: ; preds = %[[VAL_93]]
162+
// CHECK: %[[VAL_97:.*]] = add i32 %[[VAL_91]], %[[VAL_85]]
163+
// CHECK: %[[VAL_98:.*]] = mul i32 %[[VAL_97]], %[[VAL_67]]
164+
// CHECK: %[[VAL_99:.*]] = add i32 %[[VAL_98]], %[[VAL_72]]
165+
// CHECK: br label %[[VAL_100:.*]]
166+
// CHECK: omp.loop_nest.region: ; preds = %[[VAL_95]]
167+
// CHECK: %[[VAL_101:.*]] = call i32 @__kmpc_global_thread_num(ptr @1)
168+
// CHECK: %[[VAL_102:.*]] = call i32 @__kmpc_cancellationpoint(ptr @1, i32 %[[VAL_101]], i32 2)
169+
// CHECK: %[[VAL_103:.*]] = icmp eq i32 %[[VAL_102]], 0
170+
// CHECK: br i1 %[[VAL_103]], label %[[VAL_104:.*]], label %[[VAL_105:.*]]
171+
// CHECK: omp.loop_nest.region.split: ; preds = %[[VAL_100]]
172+
// CHECK: br label %[[VAL_106:.*]]
173+
// CHECK: omp.region.cont1: ; preds = %[[VAL_104]]
174+
// CHECK: br label %[[VAL_90]]
175+
// CHECK: omp_loop.inc: ; preds = %[[VAL_106]]
176+
// CHECK: %[[VAL_92]] = add nuw i32 %[[VAL_91]], 1
177+
// CHECK: br label %[[VAL_89]]
178+
// CHECK: omp_loop.exit: ; preds = %[[VAL_105]], %[[VAL_93]]
179+
// CHECK: call void @__kmpc_for_static_fini(ptr @1, i32 %[[VAL_84]])
180+
// CHECK: %[[VAL_107:.*]] = call i32 @__kmpc_global_thread_num(ptr @1)
181+
// CHECK: call void @__kmpc_barrier(ptr @2, i32 %[[VAL_107]])
182+
// CHECK: br label %[[VAL_108:.*]]
183+
// CHECK: omp_loop.after: ; preds = %[[VAL_96]]
184+
// CHECK: br label %[[VAL_109:.*]]
185+
// CHECK: omp.region.cont: ; preds = %[[VAL_108]]
186+
// CHECK: ret void
187+
// CHECK: omp.loop_nest.region.cncl: ; preds = %[[VAL_100]]
188+
// CHECK: br label %[[VAL_96]]

mlir/test/Target/LLVMIR/openmp-todo.mlir

+10-6
Original file line numberDiff line numberDiff line change
@@ -43,12 +43,16 @@ llvm.func @cancel_taskgroup() {
4343

4444
// -----
4545

46-
llvm.func @cancellation_point() {
47-
// expected-error@below {{LLVM Translation failed for operation: omp.parallel}}
48-
omp.parallel {
49-
// expected-error@below {{not yet implemented: omp.cancellation_point}}
50-
// expected-error@below {{LLVM Translation failed for operation: omp.cancellation_point}}
51-
omp.cancellation_point cancellation_construct_type(parallel)
46+
llvm.func @cancellation_point_taskgroup() {
47+
// expected-error@below {{LLVM Translation failed for operation: omp.taskgroup}}
48+
omp.taskgroup {
49+
// expected-error@below {{LLVM Translation failed for operation: omp.task}}
50+
omp.task {
51+
// expected-error@below {{not yet implemented: Unhandled clause cancel directive in omp.cancellation_point operation}}
52+
// expected-error@below {{LLVM Translation failed for operation: omp.cancellation_point}}
53+
omp.cancellation_point cancellation_construct_type(taskgroup)
54+
omp.terminator
55+
}
5256
omp.terminator
5357
}
5458
llvm.return

0 commit comments

Comments
 (0)