Skip to content

Commit c4c9f39

Browse files
authored
[MLIR][OpenMP] Fix MLIR->LLVM value matching in privatization logic (#103718)
Fixes #102935 Updates matching logic for finding the LLVM value that corresponds to an MLIR value. We need that matching to find the delayed privatizer for an LLVM value being privatized. The issue occures when there is an "indirect" correspondence between MLIR and LLVM values: in some cases the values we are trying to match stem from a pair of load/store ops that point to the same memref. This PR adds such matching logic.
1 parent f499a3f commit c4c9f39

File tree

2 files changed

+142
-28
lines changed

2 files changed

+142
-28
lines changed

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

Lines changed: 100 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -690,14 +690,14 @@ inlineOmpRegionCleanup(llvm::SmallVectorImpl<Region *> &cleanupRegions,
690690
: &builder.GetInsertBlock()->back();
691691
if (potentialTerminator && potentialTerminator->isTerminator())
692692
builder.SetInsertPoint(potentialTerminator);
693-
llvm::Value *prviateVarValue =
693+
llvm::Value *privateVarValue =
694694
shouldLoadCleanupRegionArg
695695
? builder.CreateLoad(
696696
moduleTranslation.convertType(entry.getArgument(0).getType()),
697697
privateVariables[i])
698698
: privateVariables[i];
699699

700-
moduleTranslation.mapValue(entry.getArgument(0), prviateVarValue);
700+
moduleTranslation.mapValue(entry.getArgument(0), privateVarValue);
701701

702702
if (failed(inlineConvertOmpRegions(*cleanupRegion, regionName, builder,
703703
moduleTranslation)))
@@ -1424,35 +1424,106 @@ convertOmpParallel(omp::ParallelOp opInst, llvm::IRBuilderBase &builder,
14241424
}
14251425
};
14261426

1427-
SmallVector<omp::PrivateClauseOp> privatizerClones;
1428-
SmallVector<llvm::Value *> privateVariables;
1427+
SmallVector<omp::PrivateClauseOp> mlirPrivatizerClones;
1428+
SmallVector<llvm::Value *> llvmPrivateVars;
14291429

14301430
// TODO: Perform appropriate actions according to the data-sharing
14311431
// attribute (shared, private, firstprivate, ...) of variables.
14321432
// Currently shared and private are supported.
14331433
auto privCB = [&](InsertPointTy allocaIP, InsertPointTy codeGenIP,
1434-
llvm::Value &, llvm::Value &vPtr,
1435-
llvm::Value *&replacementValue) -> InsertPointTy {
1436-
replacementValue = &vPtr;
1434+
llvm::Value &, llvm::Value &llvmOmpRegionInput,
1435+
llvm::Value *&llvmReplacementValue) -> InsertPointTy {
1436+
llvmReplacementValue = &llvmOmpRegionInput;
14371437

14381438
// If this is a private value, this lambda will return the corresponding
14391439
// mlir value and its `PrivateClauseOp`. Otherwise, empty values are
14401440
// returned.
1441-
auto [privVar, privatizerClone] =
1441+
auto [mlirPrivVar, mlirPrivatizerClone] =
14421442
[&]() -> std::pair<mlir::Value, omp::PrivateClauseOp> {
14431443
if (!opInst.getPrivateVars().empty()) {
1444-
auto privateVars = opInst.getPrivateVars();
1445-
auto privateSyms = opInst.getPrivateSyms();
1444+
auto mlirPrivVars = opInst.getPrivateVars();
1445+
auto mlirPrivSyms = opInst.getPrivateSyms();
14461446

1447-
for (auto [privVar, privatizerAttr] :
1448-
llvm::zip_equal(privateVars, *privateSyms)) {
1447+
// Try to find a privatizer that corresponds to the LLVM value being
1448+
// privatized.
1449+
for (auto [mlirPrivVar, mlirPrivatizerAttr] :
1450+
llvm::zip_equal(mlirPrivVars, *mlirPrivSyms)) {
14491451
// Find the MLIR private variable corresponding to the LLVM value
14501452
// being privatized.
1451-
llvm::Value *llvmPrivVar = moduleTranslation.lookupValue(privVar);
1452-
if (llvmPrivVar != &vPtr)
1453+
llvm::Value *mlirToLLVMPrivVar =
1454+
moduleTranslation.lookupValue(mlirPrivVar);
1455+
1456+
// Check if the LLVM value being privatized matches the LLVM value
1457+
// mapped to privVar. In some cases, this is not trivial ...
1458+
auto isMatch = [&]() {
1459+
if (mlirToLLVMPrivVar == nullptr)
1460+
return false;
1461+
1462+
// If both values are trivially equal, we found a match.
1463+
if (mlirToLLVMPrivVar == &llvmOmpRegionInput)
1464+
return true;
1465+
1466+
// Otherwise, we check if both llvmOmpRegionInputPtr and
1467+
// mlirToLLVMPrivVar refer to the same memory (through a load/store
1468+
// pair). This happens if a struct (i.e. multi-field value) is being
1469+
// privatized.
1470+
//
1471+
// For example, if the privatized value is defined by:
1472+
// ```
1473+
// %priv_val = alloca { ptr, i64 }, align 8
1474+
// ```
1475+
//
1476+
// The initialization of this value (outside the omp region) will be
1477+
// something like this:
1478+
//
1479+
// clang-format off
1480+
// ```
1481+
// %partially_init_priv_val = insertvalue { ptr, i64 } undef,
1482+
// ptr %some_ptr, 0
1483+
// %fully_init_priv_val = insertvalue { ptr, i64 } %partially_init_priv_val,
1484+
// i64 %some_i64, 1
1485+
// ...
1486+
// store { ptr, i64 } %fully_init_priv_val, ptr %priv_val, align 8
1487+
// ```
1488+
// clang-format on
1489+
//
1490+
// Now, we enter the OMP region, in order to access this privatized
1491+
// value, we need to load from the allocated memory:
1492+
// ```
1493+
// omp.par.entry:
1494+
// %priv_val_load = load { ptr, i64 }, ptr %priv_val, align 8
1495+
// ```
1496+
//
1497+
// The 2 LLVM values tracked here map as follows:
1498+
// - `mlirToLLVMPrivVar` -> `%fully_init_priv_val`
1499+
// - `llvmOmpRegionInputPtr` -> `%priv_val_load`
1500+
//
1501+
// Even though they eventually refer to the same memory reference
1502+
// (the memory being privatized), they are 2 distinct LLVM values.
1503+
// Therefore, we need to discover their correspondence by finding
1504+
// out if they store into and load from the same mem ref.
1505+
auto *llvmOmpRegionInputPtrLoad =
1506+
llvm::dyn_cast_if_present<llvm::LoadInst>(&llvmOmpRegionInput);
1507+
1508+
if (llvmOmpRegionInputPtrLoad == nullptr)
1509+
return false;
1510+
1511+
for (auto &use : mlirToLLVMPrivVar->uses()) {
1512+
auto *mlirToLLVMPrivVarStore =
1513+
llvm::dyn_cast_if_present<llvm::StoreInst>(use.getUser());
1514+
if (mlirToLLVMPrivVarStore &&
1515+
(llvmOmpRegionInputPtrLoad->getPointerOperand() ==
1516+
mlirToLLVMPrivVarStore->getPointerOperand()))
1517+
return true;
1518+
}
1519+
1520+
return false;
1521+
};
1522+
1523+
if (!isMatch())
14531524
continue;
14541525

1455-
SymbolRefAttr privSym = llvm::cast<SymbolRefAttr>(privatizerAttr);
1526+
SymbolRefAttr privSym = llvm::cast<SymbolRefAttr>(mlirPrivatizerAttr);
14561527
omp::PrivateClauseOp privatizer =
14571528
SymbolTable::lookupNearestSymbolFrom<omp::PrivateClauseOp>(
14581529
opInst, privSym);
@@ -1480,24 +1551,24 @@ convertOmpParallel(omp::ParallelOp opInst, llvm::IRBuilderBase &builder,
14801551
counter);
14811552

14821553
clone.setSymName(cloneName);
1483-
return {privVar, clone};
1554+
return {mlirPrivVar, clone};
14841555
}
14851556
}
14861557

14871558
return {mlir::Value(), omp::PrivateClauseOp()};
14881559
}();
14891560

1490-
if (privVar) {
1491-
Region &allocRegion = privatizerClone.getAllocRegion();
1561+
if (mlirPrivVar) {
1562+
Region &allocRegion = mlirPrivatizerClone.getAllocRegion();
14921563

14931564
// If this is a `firstprivate` clause, prepare the `omp.private` op by:
1494-
if (privatizerClone.getDataSharingType() ==
1565+
if (mlirPrivatizerClone.getDataSharingType() ==
14951566
omp::DataSharingClauseType::FirstPrivate) {
14961567
auto oldAllocBackBlock = std::prev(allocRegion.end());
14971568
omp::YieldOp oldAllocYieldOp =
14981569
llvm::cast<omp::YieldOp>(oldAllocBackBlock->getTerminator());
14991570

1500-
Region &copyRegion = privatizerClone.getCopyRegion();
1571+
Region &copyRegion = mlirPrivatizerClone.getCopyRegion();
15011572

15021573
mlir::IRRewriter copyCloneBuilder(&moduleTranslation.getContext());
15031574
// 1. Cloning the `copy` region to the end of the `alloc` region.
@@ -1524,7 +1595,7 @@ convertOmpParallel(omp::ParallelOp opInst, llvm::IRBuilderBase &builder,
15241595
// This way, the body of the privatizer will be changed from using the
15251596
// region/block argument to the value being privatized.
15261597
auto allocRegionArg = allocRegion.getArgument(0);
1527-
replaceAllUsesInRegionWith(allocRegionArg, privVar, allocRegion);
1598+
replaceAllUsesInRegionWith(allocRegionArg, mlirPrivVar, allocRegion);
15281599

15291600
auto oldIP = builder.saveIP();
15301601
builder.restoreIP(allocaIP);
@@ -1535,15 +1606,15 @@ convertOmpParallel(omp::ParallelOp opInst, llvm::IRBuilderBase &builder,
15351606
opInst.emitError("failed to inline `alloc` region of an `omp.private` "
15361607
"op in the parallel region");
15371608
bodyGenStatus = failure();
1538-
privatizerClone.erase();
1609+
mlirPrivatizerClone.erase();
15391610
} else {
15401611
assert(yieldedValues.size() == 1);
1541-
replacementValue = yieldedValues.front();
1612+
llvmReplacementValue = yieldedValues.front();
15421613

15431614
// Keep the LLVM replacement value and the op clone in case we need to
15441615
// emit cleanup (i.e. deallocation) logic.
1545-
privateVariables.push_back(replacementValue);
1546-
privatizerClones.push_back(privatizerClone);
1616+
llvmPrivateVars.push_back(llvmReplacementValue);
1617+
mlirPrivatizerClones.push_back(mlirPrivatizerClone);
15471618
}
15481619

15491620
builder.restoreIP(oldIP);
@@ -1571,13 +1642,14 @@ convertOmpParallel(omp::ParallelOp opInst, llvm::IRBuilderBase &builder,
15711642
bodyGenStatus = failure();
15721643

15731644
SmallVector<Region *> privateCleanupRegions;
1574-
llvm::transform(privatizerClones, std::back_inserter(privateCleanupRegions),
1645+
llvm::transform(mlirPrivatizerClones,
1646+
std::back_inserter(privateCleanupRegions),
15751647
[](omp::PrivateClauseOp privatizer) {
15761648
return &privatizer.getDeallocRegion();
15771649
});
15781650

15791651
if (failed(inlineOmpRegionCleanup(
1580-
privateCleanupRegions, privateVariables, moduleTranslation, builder,
1652+
privateCleanupRegions, llvmPrivateVars, moduleTranslation, builder,
15811653
"omp.private.dealloc", /*shouldLoadCleanupRegionArg=*/false)))
15821654
bodyGenStatus = failure();
15831655

@@ -1604,7 +1676,7 @@ convertOmpParallel(omp::ParallelOp opInst, llvm::IRBuilderBase &builder,
16041676
ompBuilder->createParallel(ompLoc, allocaIP, bodyGenCB, privCB, finiCB,
16051677
ifCond, numThreads, pbKind, isCancellable));
16061678

1607-
for (mlir::omp::PrivateClauseOp privatizerClone : privatizerClones)
1679+
for (mlir::omp::PrivateClauseOp privatizerClone : mlirPrivatizerClones)
16081680
privatizerClone.erase();
16091681

16101682
return bodyGenStatus;

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

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,3 +114,45 @@ omp.private {type = firstprivate} @multi_block.privatizer : !llvm.ptr alloc {
114114
llvm.store %arg2, %arg3 : f32, !llvm.ptr
115115
omp.yield(%arg3 : !llvm.ptr)
116116
}
117+
118+
// -----
119+
120+
// Verifies fix for https://github.com/llvm/llvm-project/issues/102935.
121+
//
122+
// The issue happens since we previously failed to match MLIR values to their
123+
// corresponding LLVM values in some cases (e.g. char strings with non-const
124+
// length).
125+
llvm.func @non_const_len_char_test(%n: !llvm.ptr {fir.bindc_name = "n"}) {
126+
%n_val = llvm.load %n : !llvm.ptr -> i64
127+
%orig_alloc = llvm.alloca %n_val x i8 {bindc_name = "str"} : (i64) -> !llvm.ptr
128+
%orig_val = llvm.mlir.undef : !llvm.struct<(ptr, i64)>
129+
%orig_val_init = llvm.insertvalue %orig_alloc, %orig_val[0] : !llvm.struct<(ptr, i64)>
130+
omp.parallel private(@non_const_len_char %orig_val_init -> %priv_arg : !llvm.struct<(ptr, i64)>) {
131+
%dummy = llvm.extractvalue %priv_arg[0] : !llvm.struct<(ptr, i64)>
132+
omp.terminator
133+
}
134+
llvm.return
135+
}
136+
137+
omp.private {type = firstprivate} @non_const_len_char : !llvm.struct<(ptr, i64)> alloc {
138+
^bb0(%orig_val: !llvm.struct<(ptr, i64)>):
139+
%str_len = llvm.extractvalue %orig_val[1] : !llvm.struct<(ptr, i64)>
140+
%priv_alloc = llvm.alloca %str_len x i8 {bindc_name = "str", pinned} : (i64) -> !llvm.ptr
141+
%priv_val = llvm.mlir.undef : !llvm.struct<(ptr, i64)>
142+
%priv_val_init = llvm.insertvalue %priv_alloc, %priv_val[0] : !llvm.struct<(ptr, i64)>
143+
omp.yield(%priv_val_init : !llvm.struct<(ptr, i64)>)
144+
} copy {
145+
^bb0(%orig_val: !llvm.struct<(ptr, i64)>, %priv_val: !llvm.struct<(ptr, i64)>):
146+
llvm.call @foo() : () -> ()
147+
omp.yield(%priv_val : !llvm.struct<(ptr, i64)>)
148+
}
149+
150+
llvm.func @foo()
151+
152+
// CHECK-LABEL: @non_const_len_char_test..omp_par({{.*}})
153+
// CHECK-NEXT: omp.par.entry:
154+
// Verify that we found the privatizer by checking that we properly inlined the
155+
// bodies of the alloc and copy regions.
156+
// CHECK: %[[STR_LEN:.*]] = extractvalue { ptr, i64 } %{{.*}}, 1
157+
// CHECK: %{{.*}} = alloca i8, i64 %[[STR_LEN]], align 1
158+
// CHECK: call void @foo()

0 commit comments

Comments
 (0)