MLIR 23.0.0git
HostOpFiltering.cpp
Go to the documentation of this file.
1//===- HostOpFiltering.cpp ------------------------------------------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8//
9// This file implements transforms to swap stack allocations on the target
10// device with device shared memory where applicable.
11//
12//===----------------------------------------------------------------------===//
13
15
19
20namespace mlir {
21namespace omp {
22#define GEN_PASS_DEF_HOSTOPFILTERINGPASS
23#include "mlir/Dialect/OpenMP/Transforms/Passes.h.inc"
24} // namespace omp
25} // namespace mlir
26
27using namespace mlir;
29/// Some host operations, like \c llvm.mlir.addressof and constants, must remain
30/// in the device module because they impact how device code is generated when
31/// attached to an \c omp.target operation.
32///
33/// This function identifies the operations that need this special handling.
34/// This includes cast-style operations to avoid losing information about the
35/// original source of an operand.
36static bool keepHostOpInDevice(Operation &op) {
37 return isPure(&op) &&
38 op.getDialect() ==
39 op.getContext()->getLoadedDialect<LLVM::LLVMDialect>();
41
42/// Add an \c omp.map.info operation and all its members recursively to the
43/// output set to be later rewritten.
44///
45/// Dependencies across \c omp.map.info are maintained by ensuring dependencies
46/// are added to the output sets before operations based on them.
47static void collectRewrite(omp::MapInfoOp mapOp,
49 for (Value member : mapOp.getMembers())
50 collectRewrite(cast<omp::MapInfoOp>(member.getDefiningOp()), rewrites);
52 rewrites.insert(mapOp);
53}
55/// Add the given value to a sorted set if it should be replaced by a
56/// placeholder when used as an operand that must remain for the device.
57///
58/// Values that are block arguments of function operations are skipped, since
59/// they will still be available after all rewrites are completed, and operands
60/// of operations that need to remain on the host are recursively collected.
61static void collectRewrite(Value value, llvm::SetVector<Value> &rewrites) {
62 if ((isa<BlockArgument>(value) &&
63 isa<FunctionOpInterface>(
64 cast<BlockArgument>(value).getOwner()->getParentOp())) ||
65 rewrites.contains(value))
66 return;
67
68 Operation *op = value.getDefiningOp();
69 if (op && keepHostOpInDevice(*op))
70 for (Value operand : op->getOperands())
71 collectRewrite(operand, rewrites);
72
73 rewrites.insert(value);
74}
75
76/// Provide the \c device_type of an \c omp.declare_target attribute, if
77/// defined.
78static std::optional<omp::DeclareTargetDeviceType>
80 auto declareTargetOp = dyn_cast<omp::DeclareTargetInterface>(op);
81 if (declareTargetOp && declareTargetOp.isDeclareTarget())
82 return declareTargetOp.getDeclareTargetDeviceType();
83 return std::nullopt;
84}
85
86namespace {
87class HostOpFilteringPass
88 : public omp::impl::HostOpFilteringPassBase<HostOpFilteringPass> {
89public:
90 HostOpFilteringPass() = default;
91
92 void runOnOperation() override {
93 auto op = dyn_cast<omp::OffloadModuleInterface>(getOperation());
94 if (!op || !op.getIsTargetDevice())
95 return;
96
97 op->walk<WalkOrder::PreOrder>([&](LLVM::LLVMFuncOp funcOp) {
98 omp::DeclareTargetDeviceType declareType =
99 getDeclareTargetDevice(*funcOp.getOperation())
100 .value_or(omp::DeclareTargetDeviceType::host);
101
102 // Only process host function definitions.
103 if (funcOp.isExternal() ||
104 declareType != omp::DeclareTargetDeviceType::host)
105 return WalkResult::advance();
106
107 if (failed(rewriteHostFunction(funcOp))) {
108 funcOp.emitOpError() << "could not filter host-only operations";
109 return WalkResult::interrupt();
110 }
111 return WalkResult::advance();
112 });
113
114 // Make non-declare target globals internal for the device. They cannot be
115 // deleted, because they are needed in order to properly lower map clauses.
116 // However, no uses will remain in the device module, so we make them
117 // internal to prevent link time redefinitions.
118 op->walk([&](LLVM::GlobalOp globalOp) {
119 if (!getDeclareTargetDevice(*globalOp.getOperation()).has_value())
120 globalOp.setLinkage(LLVM::Linkage::Internal);
121 });
122 }
123
124private:
125 /// Rewrite the given host device function containing \c omp.target
126 /// operations, to remove host-only operations that are not used by device
127 /// codegen.
128 ///
129 /// It is based on the expected form of an MLIR module lowered to where it can
130 /// be directly translated to LLVM IR and it performs the following mutations:
131 /// - Removes all returned values from the function.
132 /// - \c omp.target operations are moved to the end of the function. If they
133 /// are nested inside of any other operations, they are hoisted out of
134 /// them.
135 /// - \c depend, \c device, \c dyn_groupprivate, \c if and \c in_reduction
136 /// clauses are removed from these target functions. Values used to
137 /// initialize other clauses are replaced by placeholders as follows:
138 /// - Values defined by block arguments are replaced by placeholders only
139 /// if they are not attached to the parent function. In that case, they
140 /// are passed unmodified.
141 /// - Pure operations of the LLVM dialect are maintained, and any value
142 /// operands they might have are also replaced by placeholders following
143 /// the same rules.
144 /// - Other values are replaced by new function arguments.
145 /// - \c omp.map.info operations associated to these target regions are
146 /// preserved. These are moved above all \c omp.target and sorted to
147 /// satisfy dependencies among them.
148 /// - \c bounds arguments are removed from \c omp.map.info operations.
149 /// - \c var_ptr and \c var_ptr_ptr arguments of \c omp.map.info are
150 /// replaced by placeholders as described above.
151 /// - Every other operation not located inside of an \c omp.target is
152 /// removed.
153 LogicalResult rewriteHostFunction(LLVM::LLVMFuncOp funcOp) {
154 Region &region = funcOp.getFunctionBody();
155 LLVM::LLVMFunctionType functionType = funcOp.getFunctionType();
156
157 // Collect target operations inside of the function.
159 region.walk<WalkOrder::PreOrder>([&](Operation *op) {
160 // Skip the inside of omp.target regions, since these contain device code.
161 if (auto targetOp = dyn_cast<omp::TargetOp>(op)) {
162 targetOps.push_back(targetOp);
163 return WalkResult::skip();
164 }
165
166 // Replace omp.target_data entry block argument uses with the value used
167 // to initialize the associated omp.map.info operation. This way,
168 // references are still valid once the omp.target operation has been
169 // extracted out of the omp.target_data region.
170 if (auto targetDataOp = dyn_cast<omp::TargetDataOp>(op)) {
172 cast<omp::BlockArgOpenMPOpInterface>(*targetDataOp)
173 .getBlockArgsPairs(argPairs);
174 for (auto [operand, blockArg] : argPairs) {
175 auto mapInfo = cast<omp::MapInfoOp>(operand.getDefiningOp());
176 blockArg.replaceAllUsesWith(mapInfo.getVarPtr());
177 }
178 }
179 return WalkResult::advance();
180 });
181
182 // Make a temporary clone of the parent function with an empty region,
183 // and update all references to entry block arguments to those of the new
184 // region. Users of these arguments will later either be moved to the new
185 // region or deleted when the original region is replaced by the new.
186 OpBuilder builder(&getContext());
187 builder.setInsertionPointAfter(funcOp);
188 Operation *newFuncOp = builder.cloneWithoutRegions(funcOp);
189 Block &block = newFuncOp->getRegion(0).emplaceBlock();
190
192 locs.reserve(region.getNumArguments());
193 llvm::transform(region.getArguments(), std::back_inserter(locs),
194 [](const BlockArgument &arg) { return arg.getLoc(); });
195 block.addArguments(region.getArgumentTypes(), locs);
196
197 for (auto [oldArg, newArg] :
198 llvm::zip_equal(region.getArguments(), block.getArguments()))
199 oldArg.replaceAllUsesWith(newArg);
200
201 // Collect omp.map.info ops while satisfying interdependencies and remove
202 // operands that aren't used by target device codegen.
203 //
204 // This logic must be updated whenever operands to omp.target change.
205 llvm::SetVector<Value> rewriteValues;
207 for (omp::TargetOp targetOp : targetOps) {
208 assert(targetOp.getHostEvalVars().empty() &&
209 "unexpected host_eval in target device module");
210
211 // Variables unused by the device.
212 targetOp.getDependVarsMutable().clear();
213 targetOp.setDependKindsAttr(nullptr);
214 targetOp.getDependIteratedMutable().clear();
215 targetOp.setDependIteratedKindsAttr(nullptr);
216 targetOp.getDeviceMutable().clear();
217 targetOp.getDynGroupprivateSizeMutable().clear();
218 targetOp.getIfExprMutable().clear();
219 targetOp.getInReductionVarsMutable().clear();
220 targetOp.setInReductionByrefAttr(nullptr);
221 targetOp.setInReductionSymsAttr(nullptr);
222
223 // TODO: Clear some of these operands rather than rewriting them,
224 // depending on whether they are needed by device codegen once support for
225 // them is fully implemented.
226 for (Value allocVar : targetOp.getAllocateVars())
227 collectRewrite(allocVar, rewriteValues);
228 for (Value allocVar : targetOp.getAllocatorVars())
229 collectRewrite(allocVar, rewriteValues);
230 for (Value isDevPtr : targetOp.getIsDevicePtrVars())
231 collectRewrite(isDevPtr, rewriteValues);
232 for (Value mapVar : targetOp.getHasDeviceAddrVars())
233 collectRewrite(cast<omp::MapInfoOp>(mapVar.getDefiningOp()), mapInfos);
234 for (Value mapVar : targetOp.getMapVars())
235 collectRewrite(cast<omp::MapInfoOp>(mapVar.getDefiningOp()), mapInfos);
236 for (Value privateVar : targetOp.getPrivateVars())
237 collectRewrite(privateVar, rewriteValues);
238 for (Value threadLimit : targetOp.getThreadLimitVars())
239 collectRewrite(threadLimit, rewriteValues);
240 }
241
242 // Move omp.map.info ops to the new block and collect dependencies.
243 for (omp::MapInfoOp mapOp : mapInfos) {
244 collectRewrite(mapOp.getVarPtr(), rewriteValues);
245
246 if (Value varPtrPtr = mapOp.getVarPtrPtr())
247 collectRewrite(varPtrPtr, rewriteValues);
248
249 // Bounds are not used during target device codegen.
250 mapOp.getBoundsMutable().clear();
251 mapOp->moveBefore(&block, block.end());
252 }
253
254 builder.setInsertionPointToStart(&block);
255
256 // We don't actually need the proper initialization for all operands, but
257 // rather just to maintain the basic form of omp.target operations. We
258 // create new function arguments as placeholders for rewritten values.
259 llvm::SmallVector<Type> newFnArgTypes(functionType.getParams());
260 for (Value value : rewriteValues) {
261 Value rewriteValue;
262 Operation *definingOp = value.getDefiningOp();
263 if (definingOp && keepHostOpInDevice(*definingOp)) {
264 rewriteValue = builder.clone(*value.getDefiningOp())->getResult(0);
265 } else {
266 rewriteValue = block.addArgument(value.getType(), value.getLoc());
267 newFnArgTypes.push_back(rewriteValue.getType());
268 }
269 value.replaceAllUsesWith(rewriteValue);
270 }
271
272 // Move target operations to the end of the new block.
273 for (omp::TargetOp targetOp : targetOps)
274 targetOp->moveBefore(&block, block.end());
275
276 // Add terminator to the new block.
277 builder.setInsertionPointToEnd(&block);
278 LLVM::ReturnOp::create(builder, funcOp.getLoc(), ValueRange());
279
280 // Replace old region with the new one, now only containing the required
281 // operations, and remove the temporary operation clone.
282 region.takeBody(newFuncOp->getRegion(0));
283 newFuncOp->erase();
284
285 // Update function type after modifying the terminator and argument list.
286 funcOp.setType(LLVM::LLVMFunctionType::get(
287 LLVM::LLVMVoidType::get(&getContext()), newFnArgTypes));
288
289 return success();
290 }
291};
292} // namespace
return success()
static std::optional< omp::DeclareTargetDeviceType > getDeclareTargetDevice(Operation &op)
Provide the device_type of an omp.declare_target attribute, if defined.
static bool keepHostOpInDevice(Operation &op)
Some host operations, like llvm.mlir.addressof and constants, must remain in the device module becaus...
static void collectRewrite(omp::MapInfoOp mapOp, llvm::SetVector< omp::MapInfoOp > &rewrites)
Add an omp.map.info operation and all its members recursively to the output set to be later rewritten...
b getContext())
This class represents an argument of a Block.
Definition Value.h:306
Block represents an ordered list of Operations.
Definition Block.h:33
iterator_range< args_iterator > addArguments(TypeRange types, ArrayRef< Location > locs)
Add one argument to the argument list for each type specified in the list.
Definition Block.cpp:165
BlockArgument addArgument(Type type, Location loc)
Add one value to the argument list.
Definition Block.cpp:158
BlockArgListType getArguments()
Definition Block.h:111
iterator end()
Definition Block.h:168
Dialect * getLoadedDialect(StringRef name)
Get a registered IR dialect with the given namespace.
This class helps build Operations.
Definition Builders.h:209
Operation is the basic unit of execution within MLIR.
Definition Operation.h:87
Dialect * getDialect()
Return the dialect this operation is associated with, or nullptr if the associated dialect is not loa...
Definition Operation.h:237
Region & getRegion(unsigned index)
Returns the region held by this operation at position 'index'.
Definition Operation.h:711
Operation * cloneWithoutRegions(IRMapping &mapper)
Create a partial copy of this operation without traversing into attached regions.
operand_range getOperands()
Returns an iterator on the underlying Value's.
Definition Operation.h:403
std::enable_if_t< llvm::function_traits< std::decay_t< FnT > >::num_args==1, RetT > walk(FnT &&callback)
Walk the operation by calling the callback for each nested operation (including this one),...
Definition Operation.h:822
MLIRContext * getContext()
Return the context this operation is associated with.
Definition Operation.h:233
void erase()
Remove this operation from its parent block and delete it.
This class contains a list of basic blocks and a link to the parent operation it is attached to.
Definition Region.h:26
BlockArgListType getArguments()
Definition Region.h:94
Block & emplaceBlock()
Definition Region.h:46
unsigned getNumArguments()
Definition Region.h:136
ValueTypeRange< BlockArgListType > getArgumentTypes()
Returns the argument types of the first block within the region.
Definition Region.cpp:36
void takeBody(Region &other)
Takes body of another region (that region will have no body after this operation completes).
Definition Region.h:265
RetT walk(FnT &&callback)
Walk all nested operations, blocks or regions (including this region), depending on the type of callb...
Definition Region.h:309
This class provides an abstraction over the different types of ranges over Values.
Definition ValueRange.h:389
This class represents an instance of an SSA value in the MLIR system, representing a computable value...
Definition Value.h:96
Type getType() const
Return the type of this value.
Definition Value.h:105
void replaceAllUsesWith(Value newValue)
Replace all uses of 'this' value with the new value, updating anything in the IR that uses 'this' to ...
Definition Value.h:149
Location getLoc() const
Return the location of this value.
Definition Value.cpp:24
Operation * getDefiningOp() const
If this value is the result of an operation, return the operation that defines it.
Definition Value.cpp:18
static WalkResult skip()
Definition WalkResult.h:48
static WalkResult advance()
Definition WalkResult.h:47
static WalkResult interrupt()
Definition WalkResult.h:46
Include the generated interface declarations.
bool isPure(Operation *op)
Returns true if the given operation is pure, i.e., is speculatable that does not touch memory.