Skip to content

Commit 27639c8

Browse files
committed
[MLIR][OpenMP] Fix MLIR->LLVM value matching in privatization logic
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 698b42c commit 27639c8

File tree

2 files changed

+143
-28
lines changed

2 files changed

+143
-28
lines changed

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

Lines changed: 101 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,107 @@ 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 &llvmOmpRegionInputPtr,
1435+
llvm::Value *&llvmReplacementValue) -> InsertPointTy {
1436+
llvmReplacementValue = &llvmOmpRegionInputPtr;
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+
// prvatized.
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 == &llvmOmpRegionInputPtr)
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+
// Initialized this value (outside the omp region) will be something
1477+
// 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 the store into and load from the same mem ref.
1505+
auto *llvmOmpRegionInputPtrLoad =
1506+
llvm::dyn_cast_if_present<llvm::LoadInst>(
1507+
&llvmOmpRegionInputPtr);
1508+
1509+
if (llvmOmpRegionInputPtrLoad == nullptr)
1510+
return false;
1511+
1512+
for (auto &use : mlirToLLVMPrivVar->uses()) {
1513+
auto *mlirToLLVMPrivVarStore =
1514+
llvm::dyn_cast_if_present<llvm::StoreInst>(use.getUser());
1515+
if (mlirToLLVMPrivVarStore &&
1516+
(llvmOmpRegionInputPtrLoad->getPointerOperand() ==
1517+
mlirToLLVMPrivVarStore->getPointerOperand()))
1518+
return true;
1519+
}
1520+
1521+
return false;
1522+
};
1523+
1524+
if (!isMatch())
14531525
continue;
14541526

1455-
SymbolRefAttr privSym = llvm::cast<SymbolRefAttr>(privatizerAttr);
1527+
SymbolRefAttr privSym = llvm::cast<SymbolRefAttr>(mlirPrivatizerAttr);
14561528
omp::PrivateClauseOp privatizer =
14571529
SymbolTable::lookupNearestSymbolFrom<omp::PrivateClauseOp>(
14581530
opInst, privSym);
@@ -1480,24 +1552,24 @@ convertOmpParallel(omp::ParallelOp opInst, llvm::IRBuilderBase &builder,
14801552
counter);
14811553

14821554
clone.setSymName(cloneName);
1483-
return {privVar, clone};
1555+
return {mlirPrivVar, clone};
14841556
}
14851557
}
14861558

14871559
return {mlir::Value(), omp::PrivateClauseOp()};
14881560
}();
14891561

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

14931565
// If this is a `firstprivate` clause, prepare the `omp.private` op by:
1494-
if (privatizerClone.getDataSharingType() ==
1566+
if (mlirPrivatizerClone.getDataSharingType() ==
14951567
omp::DataSharingClauseType::FirstPrivate) {
14961568
auto oldAllocBackBlock = std::prev(allocRegion.end());
14971569
omp::YieldOp oldAllocYieldOp =
14981570
llvm::cast<omp::YieldOp>(oldAllocBackBlock->getTerminator());
14991571

1500-
Region &copyRegion = privatizerClone.getCopyRegion();
1572+
Region &copyRegion = mlirPrivatizerClone.getCopyRegion();
15011573

15021574
mlir::IRRewriter copyCloneBuilder(&moduleTranslation.getContext());
15031575
// 1. Cloning the `copy` region to the end of the `alloc` region.
@@ -1524,7 +1596,7 @@ convertOmpParallel(omp::ParallelOp opInst, llvm::IRBuilderBase &builder,
15241596
// This way, the body of the privatizer will be changed from using the
15251597
// region/block argument to the value being privatized.
15261598
auto allocRegionArg = allocRegion.getArgument(0);
1527-
replaceAllUsesInRegionWith(allocRegionArg, privVar, allocRegion);
1599+
replaceAllUsesInRegionWith(allocRegionArg, mlirPrivVar, allocRegion);
15281600

15291601
auto oldIP = builder.saveIP();
15301602
builder.restoreIP(allocaIP);
@@ -1535,15 +1607,15 @@ convertOmpParallel(omp::ParallelOp opInst, llvm::IRBuilderBase &builder,
15351607
opInst.emitError("failed to inline `alloc` region of an `omp.private` "
15361608
"op in the parallel region");
15371609
bodyGenStatus = failure();
1538-
privatizerClone.erase();
1610+
mlirPrivatizerClone.erase();
15391611
} else {
15401612
assert(yieldedValues.size() == 1);
1541-
replacementValue = yieldedValues.front();
1613+
llvmReplacementValue = yieldedValues.front();
15421614

15431615
// Keep the LLVM replacement value and the op clone in case we need to
15441616
// emit cleanup (i.e. deallocation) logic.
1545-
privateVariables.push_back(replacementValue);
1546-
privatizerClones.push_back(privatizerClone);
1617+
llvmPrivateVars.push_back(llvmReplacementValue);
1618+
mlirPrivatizerClones.push_back(mlirPrivatizerClone);
15471619
}
15481620

15491621
builder.restoreIP(oldIP);
@@ -1571,13 +1643,14 @@ convertOmpParallel(omp::ParallelOp opInst, llvm::IRBuilderBase &builder,
15711643
bodyGenStatus = failure();
15721644

15731645
SmallVector<Region *> privateCleanupRegions;
1574-
llvm::transform(privatizerClones, std::back_inserter(privateCleanupRegions),
1646+
llvm::transform(mlirPrivatizerClones,
1647+
std::back_inserter(privateCleanupRegions),
15751648
[](omp::PrivateClauseOp privatizer) {
15761649
return &privatizer.getDeallocRegion();
15771650
});
15781651

15791652
if (failed(inlineOmpRegionCleanup(
1580-
privateCleanupRegions, privateVariables, moduleTranslation, builder,
1653+
privateCleanupRegions, llvmPrivateVars, moduleTranslation, builder,
15811654
"omp.private.dealloc", /*shouldLoadCleanupRegionArg=*/false)))
15821655
bodyGenStatus = failure();
15831656

@@ -1604,7 +1677,7 @@ convertOmpParallel(omp::ParallelOp opInst, llvm::IRBuilderBase &builder,
16041677
ompBuilder->createParallel(ompLoc, allocaIP, bodyGenCB, privCB, finiCB,
16051678
ifCond, numThreads, pbKind, isCancellable));
16061679

1607-
for (mlir::omp::PrivateClauseOp privatizerClone : privatizerClones)
1680+
for (mlir::omp::PrivateClauseOp privatizerClone : mlirPrivatizerClones)
16081681
privatizerClone.erase();
16091682

16101683
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)