Enzyme main
Loading...
Searching...
No Matches
Utils.cpp
Go to the documentation of this file.
1//===- Utils.cpp - Utilities for operation interfaces
2//--------------===//
3//
4// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
5// See https://llvm.org/LICENSE.txt for license information.
6// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
7//
8//===----------------------------------------------------------------------===//
9
10#include "Interfaces/Utils.h"
11#include "Dialect/Ops.h"
15#include "Passes/Utils.h"
16#include "mlir/Analysis/AliasAnalysis.h"
17#include "mlir/Dialect/Affine/IR/AffineOps.h"
18#include "mlir/Dialect/Func/IR/FuncOps.h"
19#include "mlir/Dialect/LLVMIR/LLVMDialect.h"
20#include "mlir/Dialect/MemRef/IR/MemRef.h"
21#include "mlir/Interfaces/FunctionInterfaces.h"
22#include "mlir/Interfaces/ViewLikeInterface.h"
23#include <optional>
24
25using namespace mlir;
26using namespace mlir::enzyme;
27namespace mlir {
28namespace enzyme {
29namespace oputils {
30
31const std::set<std::string> &getNonCapturingFunctions() {
32 static std::set<std::string> NonCapturingFunctions = {
33 "free", "printf", "fprintf", "scanf",
34 "fscanf", "gettimeofday", "clock_gettime", "getenv",
35 "strrchr", "strlen", "sprintf", "sscanf",
36 "mkdir", "fwrite", "fread", "memcpy",
37 "cudaMemcpy", "memset", "cudaMemset", "__isoc99_scanf",
38 "__isoc99_fscanf"};
39 return NonCapturingFunctions;
40}
41
42bool isCaptured(Value v, Operation *potentialUser, bool *seenuse) {
43 SmallVector<Value> todo = {v};
44 while (todo.size()) {
45 Value v = todo.pop_back_val();
46 for (auto u : v.getUsers()) {
47 if (seenuse && u == potentialUser)
48 *seenuse = true;
49 if (isa<memref::LoadOp, LLVM::LoadOp, affine::AffineLoadOp>(u))
50 continue;
51 if (auto s = dyn_cast<memref::StoreOp>(u)) {
52 if (s.getValue() == v)
53 return true;
54 continue;
55 }
56 if (auto s = dyn_cast<affine::AffineStoreOp>(u)) {
57 if (s.getValue() == v)
58 return true;
59 continue;
60 }
61 if (auto s = dyn_cast<LLVM::StoreOp>(u)) {
62 if (s.getValue() == v)
63 return true;
64 continue;
65 }
66 if (auto view = dyn_cast<ViewLikeOpInterface>(u)) {
67 if (view.getViewSource() == v) {
68 todo.push_back(view.getViewDest());
69 continue;
70 }
71 }
72 if (auto sub = dyn_cast<func::ReturnOp>(u)) {
73 continue;
74 }
75 if (auto sub = dyn_cast<LLVM::MemsetOp>(u)) {
76 continue;
77 }
78 if (auto sub = dyn_cast<LLVM::MemcpyOp>(u)) {
79 continue;
80 }
81 if (auto sub = dyn_cast<LLVM::MemmoveOp>(u)) {
82 continue;
83 }
84 if (auto sub = dyn_cast<memref::DeallocOp>(u)) {
85 continue;
86 }
87 if (auto cop = dyn_cast<LLVM::CallOp>(u)) {
88 if (auto callee = cop.getCallee()) {
89 if (getNonCapturingFunctions().count(callee->str()))
90 continue;
91 }
92 }
93 if (auto cop = dyn_cast<func::CallOp>(u)) {
94 if (getNonCapturingFunctions().count(cop.getCallee().str()))
95 continue;
96 }
97 return true;
98 }
99 }
100
101 return false;
102}
103
104Value inactiveStoredValueShadow(Operation *orig, MGradientUtils &gutils,
105 Value stored, OpBuilder &builder) {
106 auto iface = cast<AutoDiffTypeInterface>(stored.getType());
107 // An immutable value carries no aliasing structure into the shadow; its
108 // tangent is simply zero.
109 if (!iface.isMutable())
110 return cast<AutoDiffTypeInterface>(gutils.getShadowType(stored.getType()))
111 .createNullValue(builder, orig->getLoc());
112 orig->emitWarning()
113 << "storing an inactive value into differentiated memory; the shadow "
114 "holds the primal value, which runtime activity would be needed to "
115 "check";
116 Value primal = gutils.getNewFromOriginal(stored);
117 if (gutils.width == 1)
118 return primal;
119 SmallVector<Value> batched(gutils.width, primal);
120 return getConcatValue(builder, orig->getLoc(), batched);
121}
122
123Value getBaseObject(Value v, bool offsetAllowed) {
124 while (Operation *def = v.getDefiningOp()) {
125 if (auto view = dyn_cast<ViewLikeOpInterface>(def)) {
126 if (!offsetAllowed) {
127 auto offsetView = dyn_cast<OffsetViewInterface>(def);
128 if (!offsetView || !offsetView.isZeroOffset())
129 break;
130 }
131 v = view.getViewSource();
132 continue;
133 }
134 break;
135 }
136 return v;
137}
138
139static bool isStackAlloca(Value v) {
140 return v.getDefiningOp<memref::AllocaOp>() ||
141 v.getDefiningOp<memref::AllocOp>() ||
142 v.getDefiningOp<LLVM::AllocaOp>();
143}
144
145bool mayAlias(Value v1, Value v2) {
146 v1 = getBaseObject(v1);
147 v2 = getBaseObject(v2);
148 if (v1 == v2)
149 return true;
150
151 // We may now assume neither v1 nor v2 are subindices
152
153 if (auto glob = v1.getDefiningOp<memref::GetGlobalOp>()) {
154 if (auto Aglob = v2.getDefiningOp<memref::GetGlobalOp>()) {
155 return glob.getName() == Aglob.getName();
156 }
157 }
158
159 if (auto glob = v1.getDefiningOp<LLVM::AddressOfOp>()) {
160 if (auto Aglob = v2.getDefiningOp<LLVM::AddressOfOp>()) {
161 return glob.getGlobalName() == Aglob.getGlobalName();
162 }
163 }
164
165 bool isAlloca[2];
166 bool isGlobal[2];
167
168 isAlloca[0] = isStackAlloca(v1);
169 isGlobal[0] = v1.getDefiningOp<memref::GetGlobalOp>() ||
170 v1.getDefiningOp<LLVM::AddressOfOp>();
171
172 isAlloca[1] = isStackAlloca(v2);
173
174 isGlobal[1] = v2.getDefiningOp<memref::GetGlobalOp>() ||
175 v2.getDefiningOp<LLVM::AddressOfOp>();
176
177 // Non-equivalent allocas/global's cannot conflict with each other
178 if ((isAlloca[0] || isGlobal[0]) && (isAlloca[1] || isGlobal[1]))
179 return false;
180
181 BlockArgument barg1 = dyn_cast<BlockArgument>(v1);
182 BlockArgument barg2 = dyn_cast<BlockArgument>(v2);
183
184 FunctionOpInterface f1 =
185 barg1 ? dyn_cast<FunctionOpInterface>(barg1.getOwner()->getParentOp())
186 : nullptr;
187 FunctionOpInterface f2 =
188 barg2 ? dyn_cast<FunctionOpInterface>(barg2.getOwner()->getParentOp())
189 : nullptr;
190
191 bool isNoAlias1 =
192 f1 ? !!f1.getArgAttr(barg1.getArgNumber(),
193 LLVM::LLVMDialect::getNoAliasAttrName())
194 : false;
195 bool isNoAlias2 =
196 f2 ? !!f2.getArgAttr(barg2.getArgNumber(),
197 LLVM::LLVMDialect::getNoAliasAttrName())
198 : false;
199
200 if (!isCaptured(v1) && isNoAlias1)
201 return false;
202 if (!isCaptured(v2) && isNoAlias2)
203 return false;
204
205 bool isArg[2];
206 isArg[0] = f1;
207 isArg[1] = f2;
208
209 // Stack allocations cannot have been passed as an argument.
210 if ((isAlloca[0] && isArg[1]) || (isAlloca[1] && isArg[0]))
211 return false;
212
213 // Non captured base allocas cannot conflict with another base value.
214 if (isAlloca[0] && !isCaptured(v1))
215 return false;
216
217 if (isAlloca[1] && !isCaptured(v2))
218 return false;
219
220 return true;
221}
222
223bool mayAlias(MemoryEffects::EffectInstance a, Value v2) {
224 if (Value v = a.getValue()) {
225 return mayAlias(v, v2);
226 }
227 return true;
228}
229
230bool mayAlias(MemoryEffects::EffectInstance &a,
231 MemoryEffects::EffectInstance &b) {
232 if (a.getResource()->getResourceID() != b.getResource()->getResourceID())
233 return false;
234 Value valA = a.getValue();
235 Value valB = b.getValue();
236
237 // unknown effects may always alias
238 if (!valA || !valB) {
239 return true;
240 }
241
242 auto valResult = oputils::mayAlias(valA, valB);
243 return valResult;
244}
245
246bool isReadOnly(Operation *op) {
247 // If the op has memory effects, try to characterize them to see if the op
248 // is trivially dead here.
249 if (auto effectInterface = dyn_cast<MemoryEffectOpInterface>(op)) {
250 // Check to see if this op either has no effects, or only reads from memory.
251 SmallVector<MemoryEffects::EffectInstance, 1> effects;
252 effectInterface.getEffects(effects);
253 if (!llvm::all_of(effects, [](const MemoryEffects::EffectInstance &it) {
254 return isa<MemoryEffects::Read>(it.getEffect());
255 })) {
256 return false;
257 }
258 }
259
260 bool isRecursiveContainer =
261 op->hasTrait<OpTrait::HasRecursiveMemoryEffects>();
262 if (isRecursiveContainer) {
263 for (Region &region : op->getRegions()) {
264 for (auto &block : region) {
265 for (auto &nestedOp : block)
266 if (!isReadOnly(&nestedOp))
267 return false;
268 }
269 }
270 }
271
272 return true;
273}
274
275bool isReadNone(Operation *op) {
276 bool hasRecursiveEffects = op->hasTrait<OpTrait::HasRecursiveMemoryEffects>();
277 if (hasRecursiveEffects) {
278 for (Region &region : op->getRegions()) {
279 for (auto &block : region) {
280 for (auto &nestedOp : block)
281 if (!isReadNone(&nestedOp))
282 return false;
283 }
284 }
285 return true;
286 }
287
288 // If the op has memory effects, try to characterize them to see if the op
289 // is trivially dead here.
290 if (auto effectInterface = dyn_cast<MemoryEffectOpInterface>(op)) {
291 // Check to see if this op either has no effects, or only allocates/reads
292 // memory.
293 SmallVector<MemoryEffects::EffectInstance, 1> effects;
294 effectInterface.getEffects(effects);
295 if (llvm::any_of(effects, [](const MemoryEffects::EffectInstance &it) {
296 return isa<MemoryEffects::Read>(it.getEffect()) ||
297 isa<MemoryEffects::Write>(it.getEffect());
298 })) {
299 return false;
300 }
301 return true;
302 }
303 return false;
304}
305
306bool collectOpEffects(Operation *rootOp,
307 SmallVector<MemoryEffects::EffectInstance> &effects) {
308 SmallVector<Operation *> effectingOps(1, rootOp);
309 bool couldCollectEffects = true;
310
311 while (!effectingOps.empty()) {
312 Operation *op = effectingOps.pop_back_val();
313 bool isRecursiveContainer =
314 op->hasTrait<OpTrait::HasRecursiveMemoryEffects>();
315
316 if (isRecursiveContainer) {
317 for (Region &region : op->getRegions()) {
318 for (Block &block : region) {
319 for (Operation &nestedOp : block) {
320 effectingOps.push_back(&nestedOp);
321 }
322 }
323 }
324 }
325
326 if (auto effectInterface = dyn_cast<MemoryEffectOpInterface>(op)) {
327 SmallVector<MemoryEffects::EffectInstance> localEffects;
328 effectInterface.getEffects(localEffects);
329 llvm::append_range(effects, localEffects);
330 } else if (!isRecursiveContainer) {
331 // Handle specific operations which are not recursive containers, but
332 // still may have memory effects(eg. autodiff calls, llvm calls to libc
333 // functions). If it's none of these, then the operation may not have any
334 // memory effects
335 if (auto cop = dyn_cast<LLVM::CallOp>(op)) {
336 if (auto callee = cop.getCallee()) {
337 if (*callee == "scanf" || *callee == "__isoc99_scanf") {
338 // Global read
339 effects.emplace_back(
340 MemoryEffects::Effect::get<MemoryEffects::Read>());
341
342 bool first = true;
343 for (auto &arg : cop.getArgOperandsMutable()) {
344 if (first)
345 effects.emplace_back(MemoryEffects::Read::get(), &arg);
346 else
347 effects.emplace_back(MemoryEffects::Write::get(), &arg,
348 SideEffects::DefaultResource::get());
349 first = false;
350 }
351 }
352 if (*callee == "fscanf" || *callee == "__isoc99_fscanf") {
353 // Global read
354 effects.emplace_back(
355 MemoryEffects::Effect::get<MemoryEffects::Read>());
356
357 for (auto &&[idx, arg] :
358 llvm::enumerate(cop.getArgOperandsMutable())) {
359 if (idx == 0) {
360 effects.emplace_back(MemoryEffects::Read::get(), &arg,
361 SideEffects::DefaultResource::get());
362 effects.emplace_back(MemoryEffects::Write::get(), &arg,
363 SideEffects::DefaultResource::get());
364 } else if (idx == 1) {
365 effects.emplace_back(MemoryEffects::Read::get(), &arg,
366 SideEffects::DefaultResource::get());
367 } else
368 effects.emplace_back(MemoryEffects::Write::get(), &arg,
369 SideEffects::DefaultResource::get());
370 }
371 }
372 if (*callee == "printf") {
373 // Global read
374 effects.emplace_back(
375 MemoryEffects::Effect::get<MemoryEffects::Write>());
376 for (auto &arg : cop.getArgOperandsMutable()) {
377 effects.emplace_back(MemoryEffects::Read::get(), &arg,
378 SideEffects::DefaultResource::get());
379 }
380 }
381 if (*callee == "free") {
382 for (auto &arg : cop.getArgOperandsMutable()) {
383 effects.emplace_back(MemoryEffects::Free::get(), &arg,
384 SideEffects::DefaultResource::get());
385 }
386 }
387 if (*callee == "strlen") {
388 for (auto &arg : cop.getArgOperandsMutable()) {
389 effects.emplace_back(MemoryEffects::Read::get(), &arg,
390 SideEffects::DefaultResource::get());
391 }
392 }
393 }
394 } else {
395 // TODO: handle AutoDiffOp, ForwardDiffOp and AutoDiffRegionOp effects.
396 // Just conservatively add all effects for now
397
398 // We need to be conservative here in case the op doesn't have the
399 // interface and assume it can have any possible effect.
400
401 effects.emplace_back(MemoryEffects::Effect::get<MemoryEffects::Read>());
402 effects.emplace_back(
403 MemoryEffects::Effect::get<MemoryEffects::Write>());
404 effects.emplace_back(
405 MemoryEffects::Effect::get<MemoryEffects::Allocate>());
406 effects.emplace_back(MemoryEffects::Effect::get<MemoryEffects::Free>());
407 couldCollectEffects = false;
408
409 // no use in exploring other ops so break
410 break;
411 }
412 }
413 }
414 return couldCollectEffects;
415}
416
417SmallVector<MemoryEffects::EffectInstance>
418collectFnEffects(FunctionOpInterface fnOp) {
419 SmallVector<MemoryEffects::EffectInstance> innerEffects;
420 for (auto &blk : fnOp.getBlocks()) {
421 for (auto &op : blk) {
422 SmallVector<MemoryEffects::EffectInstance> opEffects;
423 (void)collectOpEffects(&op, opEffects);
424 innerEffects.append(opEffects.begin(), opEffects.end());
425 }
426 }
427
428 return innerEffects;
429}
430
431MemoryEffects::EffectInstance getEffectOfVal(Value val,
432 MemoryEffects::Effect *effect,
433 SideEffects::Resource *resource) {
434
435 if (auto valOR = dyn_cast<OpResult>(val))
436 return MemoryEffects::EffectInstance(effect, valOR, resource);
437 else if (auto valBA = dyn_cast<BlockArgument>(val)) {
438 return MemoryEffects::EffectInstance(effect, valBA, resource);
439 } else {
440 llvm_unreachable("Provided Value is neither an argument nor a result of an "
441 "op. This is not allowed by SSA");
442 return nullptr;
443 }
444}
445
446} // namespace oputils
447} // namespace enzyme
448} // namespace mlir
mlir::Type getShadowType(mlir::Type T)
SmallVector< mlir::Value, 1 > getNewFromOriginal(ValueRange originst) const
bool mayAlias(Value v1, Value v2)
Definition Utils.cpp:145
static bool isStackAlloca(Value v)
Definition Utils.cpp:139
bool isCaptured(Value v, Operation *potentialUser, bool *seenuse)
Definition Utils.cpp:42
const std::set< std::string > & getNonCapturingFunctions()
Definition Utils.cpp:31
Value getBaseObject(Value v, bool offsetAllowed)
Definition Utils.cpp:123
bool collectOpEffects(Operation *rootOp, SmallVector< MemoryEffects::EffectInstance > &effects)
Returns the side effects of an operation(similar to mlir::getEffectsRecursively).
Definition Utils.cpp:306
SmallVector< MemoryEffects::EffectInstance > collectFnEffects(FunctionOpInterface fnOp)
Definition Utils.cpp:418
MemoryEffects::EffectInstance getEffectOfVal(Value val, MemoryEffects::Effect *effect, SideEffects::Resource *resource)
Definition Utils.cpp:431
bool isReadNone(Operation *op)
Definition Utils.cpp:275
Value inactiveStoredValueShadow(Operation *orig, MGradientUtils &gutils, Value stored, OpBuilder &builder)
Definition Utils.cpp:104
bool isReadOnly(Operation *op)
Definition Utils.cpp:246
Value getConcatValue(OpBuilder &builder, Location loc, ArrayRef< Value > argList)
Definition Utils.cpp:155