Skip to content

Commit a1f2fb6

Browse files
authored
[MLIR][OpenMP] Prevent composite omp.simd related crashes (#113680)
This patch updates the translation of `omp.wsloop` with a nested `omp.simd` to prevent uses of block arguments defined by the latter from triggering null pointer dereferences. This happens because the inner `omp.simd` operation representing composite `do simd` constructs is currently skipped and not translated, but this results in block arguments defined by it not being mapped to an LLVM value. The proposed solution is to map these block arguments to the LLVM value associated to the corresponding operand, which is defined above.
1 parent b05fec9 commit a1f2fb6

File tree

2 files changed

+143
-3
lines changed

2 files changed

+143
-3
lines changed

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

Lines changed: 63 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,62 @@ static llvm::omp::ProcBindKind getProcBindKind(omp::ClauseProcBindKind kind) {
262262
llvm_unreachable("Unknown ClauseProcBindKind kind");
263263
}
264264

265+
/// Helper function to map block arguments defined by ignored loop wrappers to
266+
/// LLVM values and prevent any uses of those from triggering null pointer
267+
/// dereferences.
268+
///
269+
/// This must be called after block arguments of parent wrappers have already
270+
/// been mapped to LLVM IR values.
271+
static LogicalResult
272+
convertIgnoredWrapper(omp::LoopWrapperInterface &opInst,
273+
LLVM::ModuleTranslation &moduleTranslation) {
274+
// Map block arguments directly to the LLVM value associated to the
275+
// corresponding operand. This is semantically equivalent to this wrapper not
276+
// being present.
277+
auto forwardArgs =
278+
[&moduleTranslation](llvm::ArrayRef<BlockArgument> blockArgs,
279+
OperandRange operands) {
280+
for (auto [arg, var] : llvm::zip_equal(blockArgs, operands))
281+
moduleTranslation.mapValue(arg, moduleTranslation.lookupValue(var));
282+
};
283+
284+
return llvm::TypeSwitch<Operation *, LogicalResult>(opInst)
285+
.Case([&](omp::SimdOp op) {
286+
auto blockArgIface = cast<omp::BlockArgOpenMPOpInterface>(*op);
287+
forwardArgs(blockArgIface.getPrivateBlockArgs(), op.getPrivateVars());
288+
forwardArgs(blockArgIface.getReductionBlockArgs(),
289+
op.getReductionVars());
290+
return success();
291+
})
292+
.Default([&](Operation *op) {
293+
return op->emitError() << "cannot ignore nested wrapper";
294+
});
295+
}
296+
297+
/// Helper function to call \c convertIgnoredWrapper() for all wrappers of the
298+
/// given \c loopOp nested inside of \c parentOp. This has the effect of mapping
299+
/// entry block arguments defined by these operations to outside values.
300+
///
301+
/// It must be called after block arguments of \c parentOp have already been
302+
/// mapped themselves.
303+
static LogicalResult
304+
convertIgnoredWrappers(omp::LoopNestOp loopOp,
305+
omp::LoopWrapperInterface parentOp,
306+
LLVM::ModuleTranslation &moduleTranslation) {
307+
SmallVector<omp::LoopWrapperInterface> wrappers;
308+
loopOp.gatherWrappers(wrappers);
309+
310+
// Process wrappers nested inside of `parentOp` from outermost to innermost.
311+
for (auto it =
312+
std::next(std::find(wrappers.rbegin(), wrappers.rend(), parentOp));
313+
it != wrappers.rend(); ++it) {
314+
if (failed(convertIgnoredWrapper(*it, moduleTranslation)))
315+
return failure();
316+
}
317+
318+
return success();
319+
}
320+
265321
/// Converts an OpenMP 'masked' operation into LLVM IR using OpenMPIRBuilder.
266322
static LogicalResult
267323
convertOmpMasked(Operation &opInst, llvm::IRBuilderBase &builder,
@@ -1262,9 +1318,6 @@ convertOmpWsloop(Operation &opInst, llvm::IRBuilderBase &builder,
12621318
!wsloopOp.getPrivateVars().empty() || wsloopOp.getPrivateSyms())
12631319
return opInst.emitError("unhandled clauses for translation to LLVM IR");
12641320

1265-
// FIXME: Here any other nested wrappers (e.g. omp.simd) are skipped, so
1266-
// codegen for composite constructs like 'DO/FOR SIMD' will be the same as for
1267-
// 'DO/FOR'.
12681321
auto loopOp = cast<omp::LoopNestOp>(wsloopOp.getWrappedLoop());
12691322

12701323
llvm::ArrayRef<bool> isByRef = getIsByRef(wsloopOp.getReductionByref());
@@ -1302,6 +1355,13 @@ convertOmpWsloop(Operation &opInst, llvm::IRBuilderBase &builder,
13021355
isByRef)))
13031356
return failure();
13041357

1358+
// TODO: Replace this with proper composite translation support.
1359+
// Currently, all nested wrappers are ignored, so 'do/for simd' will be
1360+
// treated the same as a standalone 'do/for'. This is allowed by the spec,
1361+
// since it's equivalent to always using a SIMD length of 1.
1362+
if (failed(convertIgnoredWrappers(loopOp, wsloopOp, moduleTranslation)))
1363+
return failure();
1364+
13051365
// Store the mapping between reduction variables and their private copies on
13061366
// ModuleTranslation stack. It can be then recovered when translating
13071367
// omp.reduce operations in a separate call.

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

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -586,3 +586,83 @@ llvm.func @parallel_nested_workshare_reduction(%ub : i64) {
586586
// Reduction function.
587587
// CHECK: define internal void @[[REDFUNC]]
588588
// CHECK: add i32
589+
590+
// -----
591+
592+
omp.declare_reduction @add_f32 : f32
593+
init {
594+
^bb0(%arg: f32):
595+
%0 = llvm.mlir.constant(0.0 : f32) : f32
596+
omp.yield (%0 : f32)
597+
}
598+
combiner {
599+
^bb1(%arg0: f32, %arg1: f32):
600+
%1 = llvm.fadd %arg0, %arg1 : f32
601+
omp.yield (%1 : f32)
602+
}
603+
atomic {
604+
^bb2(%arg2: !llvm.ptr, %arg3: !llvm.ptr):
605+
%2 = llvm.load %arg3 : !llvm.ptr -> f32
606+
llvm.atomicrmw fadd %arg2, %2 monotonic : !llvm.ptr, f32
607+
omp.yield
608+
}
609+
610+
// CHECK-LABEL: @wsloop_simd_reduction
611+
llvm.func @wsloop_simd_reduction(%lb : i64, %ub : i64, %step : i64) {
612+
%c1 = llvm.mlir.constant(1 : i32) : i32
613+
%0 = llvm.alloca %c1 x i32 : (i32) -> !llvm.ptr
614+
omp.parallel {
615+
omp.wsloop reduction(@add_f32 %0 -> %prv1 : !llvm.ptr) {
616+
omp.simd reduction(@add_f32 %prv1 -> %prv2 : !llvm.ptr) {
617+
omp.loop_nest (%iv) : i64 = (%lb) to (%ub) step (%step) {
618+
%1 = llvm.mlir.constant(2.0 : f32) : f32
619+
%2 = llvm.load %prv2 : !llvm.ptr -> f32
620+
%3 = llvm.fadd %1, %2 : f32
621+
llvm.store %3, %prv2 : f32, !llvm.ptr
622+
omp.yield
623+
}
624+
} {omp.composite}
625+
} {omp.composite}
626+
omp.terminator
627+
}
628+
llvm.return
629+
}
630+
631+
// Same checks as for wsloop reduction, because currently omp.simd is ignored in
632+
// a composite 'do/for simd' construct.
633+
// Call to the outlined function.
634+
// CHECK: call void {{.*}} @__kmpc_fork_call
635+
// CHECK-SAME: @[[OUTLINED:[A-Za-z_.][A-Za-z0-9_.]*]]
636+
637+
// Outlined function.
638+
// CHECK: define internal void @[[OUTLINED]]
639+
640+
// Private reduction variable and its initialization.
641+
// CHECK: %[[PRIVATE:.+]] = alloca float
642+
// CHECK: store float 0.000000e+00, ptr %[[PRIVATE]]
643+
644+
// Call to the reduction function.
645+
// CHECK: call i32 @__kmpc_reduce
646+
// CHECK-SAME: @[[REDFUNC:[A-Za-z_.][A-Za-z0-9_.]*]]
647+
648+
// Atomic reduction.
649+
// CHECK: %[[PARTIAL:.+]] = load float, ptr %[[PRIVATE]]
650+
// CHECK: atomicrmw fadd ptr %{{.*}}, float %[[PARTIAL]]
651+
652+
// Non-atomic reduction:
653+
// CHECK: fadd float
654+
// CHECK: call void @__kmpc_end_reduce
655+
// CHECK: br label %[[FINALIZE:.+]]
656+
657+
// CHECK: [[FINALIZE]]:
658+
// CHECK: call void @__kmpc_barrier
659+
660+
// Update of the private variable using the reduction region
661+
// (the body block currently comes after all the other blocks).
662+
// CHECK: %[[PARTIAL:.+]] = load float, ptr %[[PRIVATE]]
663+
// CHECK: %[[UPDATED:.+]] = fadd float 2.000000e+00, %[[PARTIAL]]
664+
// CHECK: store float %[[UPDATED]], ptr %[[PRIVATE]]
665+
666+
// Reduction function.
667+
// CHECK: define internal void @[[REDFUNC]]
668+
// CHECK: fadd float

0 commit comments

Comments
 (0)