Enzyme main
Loading...
Searching...
No Matches
ActivityAnalysis.cpp
Go to the documentation of this file.
1#include "ActivityAnalysis.h"
2#include "Dialect/Dialect.h"
4#include "Interfaces/Utils.h"
5#include "mlir/Dialect/Func/IR/FuncOps.h"
6#include "mlir/Dialect/LLVMIR/LLVMDialect.h"
7#include "mlir/Dialect/LLVMIR/LLVMTypes.h"
8#include "mlir/Dialect/MemRef/IR/MemRef.h"
9#include "mlir/IR/BuiltinAttributes.h"
10#include "mlir/IR/Matchers.h"
11#include "mlir/IR/Region.h"
12#include "mlir/IR/RegionKindInterface.h"
13#include "mlir/IR/SymbolTable.h"
14#include "mlir/Interfaces/CallInterfaces.h"
15#include "mlir/Interfaces/CastInterfaces.h"
16#include "mlir/Interfaces/ControlFlowInterfaces.h"
17#include "mlir/Interfaces/SideEffectInterfaces.h"
18#include "mlir/Interfaces/ViewLikeInterface.h"
19#include "llvm/ADT/STLExtras.h"
20#include "llvm/Demangle/Demangle.h"
21#include "llvm/IR/Intrinsics.h"
22#include "llvm/Support/ModRef.h"
23
25
26static const char *KnownInactiveFunctionsStartingWith[] = {
27 "f90io",
28 "$ss5print",
29 "_ZTv0_n24_NSoD", //"1Ev, 0Ev
30 "_ZNSt16allocator_traitsISaIdEE10deallocate",
31 "_ZNSaIcED1Ev",
32 "_ZNSaIcEC1Ev",
33};
34
35static const char *KnownInactiveFunctionsContains[] = {
36 "__enzyme_float", "__enzyme_double", "__enzyme_integer",
37 "__enzyme_pointer"};
38
39static const std::set<std::string> InactiveGlobals = {
40 "ompi_request_null", "ompi_mpi_double", "ompi_mpi_comm_world", "stderr",
41 "stdout", "stdin", "_ZSt3cin", "_ZSt4cout", "_ZSt5wcout", "_ZSt4cerr",
42 "_ZTVNSt7__cxx1115basic_stringbufIcSt11char_traitsIcESaIcEEE",
43 "_ZTVSt15basic_streambufIcSt11char_traitsIcEE",
44 "_ZTVSt9basic_iosIcSt11char_traitsIcEE",
45 // istream
46 "_ZTVNSt7__cxx1119basic_istringstreamIcSt11char_traitsIcESaIcEEE",
47 "_ZTTNSt7__cxx1119basic_istringstreamIcSt11char_traitsIcESaIcEEE",
48 // ostream
49 "_ZTVNSt7__cxx1119basic_ostringstreamIcSt11char_traitsIcESaIcEEE",
50 "_ZTTNSt7__cxx1119basic_ostringstreamIcSt11char_traitsIcESaIcEEE",
51 // stringstream
52 "_ZTVNSt7__cxx1118basic_stringstreamIcSt11char_traitsIcESaIcEEE",
53 "_ZTTNSt7__cxx1118basic_stringstreamIcSt11char_traitsIcESaIcEEE",
54 // ifstream
55 "_ZTTSt14basic_ifstreamIcSt11char_traitsIcEE",
56 // ofstream
57 "_ZTTSt14basic_ofstreamIcSt11char_traitsIcEE",
58 // vtable for __cxxabiv1::__si_class_type_info
59 "_ZTVN10__cxxabiv120__si_class_type_infoE",
60 "_ZTVN10__cxxabiv117__class_type_infoE",
61 "_ZTVN10__cxxabiv121__vmi_class_type_infoE"};
62
63static const std::map<std::string, size_t> MPIInactiveCommAllocators = {
64 {"MPI_Graph_create", 5},
65 {"MPI_Comm_split", 2},
66 {"MPI_Intercomm_create", 6},
67 {"MPI_Comm_spawn", 6},
68 {"MPI_Comm_spawn_multiple", 7},
69 {"MPI_Comm_accept", 4},
70 {"MPI_Comm_connect", 4},
71 {"MPI_Comm_create", 2},
72 {"MPI_Comm_create_group", 3},
73 {"MPI_Comm_dup", 1},
74 {"MPI_Comm_dup", 2},
75 {"MPI_Comm_idup", 1},
76 {"MPI_Comm_join", 1},
77};
78
79// Instructions which themselves are inactive
80// the returned value, however, may still be active
81static const std::set<std::string> KnownInactiveFunctionInsts = {
82 "__dynamic_cast",
83 "_ZSt18_Rb_tree_decrementPKSt18_Rb_tree_node_base",
84 "_ZSt18_Rb_tree_incrementPKSt18_Rb_tree_node_base",
85 "_ZSt18_Rb_tree_decrementPSt18_Rb_tree_node_base",
86 "_ZSt18_Rb_tree_incrementPSt18_Rb_tree_node_base",
87 "jl_ptr_to_array",
88 "jl_ptr_to_array_1d"};
89
90static const std::set<std::string> KnownInactiveFunctions = {
91 "abort",
92 "time",
93 "memcmp",
94 "gettimeofday",
95 "stat",
96 "mkdir",
97 "compress2",
98 "__assert_fail",
99 "__cxa_atexit",
100 "__cxa_guard_acquire",
101 "__cxa_guard_release",
102 "__cxa_guard_abort",
103 "snprintf",
104 "sprintf",
105 "printf",
106 "fprintf",
107 "putchar",
108 "fprintf",
109 "vprintf",
110 "vsnprintf",
111 "puts",
112 "fputc",
113 "fflush",
114 "__kmpc_for_static_init_4",
115 "__kmpc_for_static_init_4u",
116 "__kmpc_for_static_init_8",
117 "__kmpc_for_static_init_8u",
118 "__kmpc_for_static_fini",
119 "__kmpc_dispatch_init_4",
120 "__kmpc_dispatch_init_4u",
121 "__kmpc_dispatch_init_8",
122 "__kmpc_dispatch_init_8u",
123 "__kmpc_dispatch_next_4",
124 "__kmpc_dispatch_next_4u",
125 "__kmpc_dispatch_next_8",
126 "__kmpc_dispatch_next_8u",
127 "__kmpc_dispatch_fini_4",
128 "__kmpc_dispatch_fini_4u",
129 "__kmpc_dispatch_fini_8",
130 "__kmpc_dispatch_fini_8u",
131 "__kmpc_barrier",
132 "__kmpc_barrier_master",
133 "__kmpc_barrier_master_nowait",
134 "__kmpc_barrier_end_barrier_master",
135 "__kmpc_global_thread_num",
136 "omp_get_max_threads",
137 "malloc_usable_size",
138 "malloc_size",
139 "MPI_Init",
140 "MPI_Comm_size",
141 "PMPI_Comm_size",
142 "MPI_Comm_rank",
143 "PMPI_Comm_rank",
144 "MPI_Get_processor_name",
145 "MPI_Finalize",
146 "MPI_Test",
147 "MPI_Probe", // double check potential syncronization
148 "MPI_Barrier",
149 "MPI_Abort",
150 "MPI_Get_count",
151 "MPI_Comm_free",
152 "MPI_Comm_get_parent",
153 "MPI_Comm_get_name",
154 "MPI_Comm_get_info",
155 "MPI_Comm_remote_size",
156 "MPI_Comm_set_info",
157 "MPI_Comm_set_name",
158 "MPI_Comm_compare",
159 "MPI_Comm_call_errhandler",
160 "MPI_Comm_create_errhandler",
161 "MPI_Comm_disconnect",
162 "MPI_Wtime",
163 "_msize",
164 "ftnio_fmt_write64",
165 "f90_strcmp_klen",
166 "__swift_instantiateConcreteTypeFromMangledName",
167 "logb",
168 "logbf",
169 "logbl",
170};
171
173 // TODO this returns allocated memory and thus can be an active value
174 // "std::allocator",
175 "std::string",
176 "std::cerr",
177 "std::istream",
178 "std::ostream",
179 "std::ios_base",
180 "std::locale",
181 "std::ctype<char>",
182 "std::__basic_file",
183 "std::__ioinit",
184 "std::__basic_file",
185
186 // __cxx11
187 "std::__cxx11::basic_string",
188 "std::__cxx11::basic_ios",
189 "std::__cxx11::basic_ostringstream",
190 "std::__cxx11::basic_istringstream",
191 "std::__cxx11::basic_istream",
192 "std::__cxx11::basic_ostream",
193 "std::__cxx11::basic_ifstream",
194 "std::__cxx11::basic_ofstream",
195 "std::__cxx11::basic_stringbuf",
196 "std::__cxx11::basic_filebuf",
197 "std::__cxx11::basic_streambuf",
198
199 // non __cxx11
200 "std::basic_string",
201 "std::basic_ios",
202 "std::basic_ostringstream",
203 "std::basic_istringstream",
204 "std::basic_istream",
205 "std::basic_ostream",
206 "std::basic_ifstream",
207 "std::basic_ofstream",
208 "std::basic_stringbuf",
209 "std::basic_filebuf",
210 "std::basic_streambuf",
211
212};
213
214// TODO: these should be lifted to proper operations and implemented via the
215// interface.
216const static unsigned constantIntrinsics[] = {
217#if LLVM_VERSION_MAJOR <= 20
218 llvm::Intrinsic::nvvm_barrier0,
219#else
220 llvm::Intrinsic::nvvm_barrier_cta_sync_aligned_all,
221 llvm::Intrinsic::nvvm_barrier_cta_sync_aligned_count,
222#endif
223#if LLVM_VERSION_MAJOR < 22
224 llvm::Intrinsic::nvvm_barrier0_popc,
225 llvm::Intrinsic::nvvm_barrier0_and,
226 llvm::Intrinsic::nvvm_barrier0_or,
227#else
228 llvm::Intrinsic::nvvm_barrier_cta_red_and_aligned_all,
229 llvm::Intrinsic::nvvm_barrier_cta_red_and_aligned_count,
230 llvm::Intrinsic::nvvm_barrier_cta_red_or_aligned_all,
231 llvm::Intrinsic::nvvm_barrier_cta_red_or_aligned_count,
232 llvm::Intrinsic::nvvm_barrier_cta_red_popc_aligned_all,
233 llvm::Intrinsic::nvvm_barrier_cta_red_popc_aligned_count,
234#endif
235 llvm::Intrinsic::nvvm_membar_cta,
236 llvm::Intrinsic::nvvm_membar_gl,
237 llvm::Intrinsic::nvvm_membar_sys,
238 llvm::Intrinsic::amdgcn_s_barrier,
239 llvm::Intrinsic::assume,
240 llvm::Intrinsic::stacksave,
241 llvm::Intrinsic::stackrestore,
242 llvm::Intrinsic::lifetime_start,
243 llvm::Intrinsic::lifetime_end,
244 // llvm::Intrinsic::dbg_addr,
245 llvm::Intrinsic::dbg_declare,
246 llvm::Intrinsic::dbg_value,
247 llvm::Intrinsic::invariant_start,
248 llvm::Intrinsic::invariant_end,
249 llvm::Intrinsic::var_annotation,
250 llvm::Intrinsic::ptr_annotation,
251 llvm::Intrinsic::annotation,
252 llvm::Intrinsic::codeview_annotation,
253 llvm::Intrinsic::expect,
254 llvm::Intrinsic::type_test,
255 llvm::Intrinsic::donothing,
256 llvm::Intrinsic::prefetch,
257 llvm::Intrinsic::trap,
258 llvm::Intrinsic::is_constant,
259};
260
262 DataFlowSolver &solver, FunctionOpInterface funcOp,
263 ArrayRef<DIFFE_TYPE> argActivity, ArrayRef<DIFFE_TYPE> returnActivity)
264 : funcOp(funcOp), solver(solver), p2sets(nullptr),
265 forwardOriginsMap(nullptr), backwardOriginsMap(nullptr),
266 argActivity(argActivity), returnActivity(returnActivity) {
267
268 // Do things naively for now, computing the dataflow states multiple times
269 SymbolTableCollection symbolTable;
270 SmallVector<CallableOpInterface> sorted;
271 // TODO: Fallback to a whole module activity analysis in the presence of
272 // cycles
273 (void)reverseToposortCallgraph(funcOp, &symbolTable, sorted);
274
275 StringRef pointerSummaryName = EnzymeDialect::getPointerSummaryAttrName();
276 for (CallableOpInterface node : sorted) {
277 // A serialized summary lets CALLERS of this function reuse the analysis,
278 // but the function being differentiated needs its own per-value solver
279 // states regardless: nested differentiation reaches here with funcOp
280 // already summarized by an enclosing analysis, and skipping it left
281 // every value looking inactive -- the second derivative of anything was
282 // zero. Strip the stale self-summary first so the fresh analysis does
283 // not read it as callee information about itself.
284 if (!node.getCallableRegion() ||
285 (node->hasAttr(pointerSummaryName) && node.getOperation() != funcOp))
286 continue;
287 if (node.getOperation() == funcOp && node->hasAttr(pointerSummaryName))
288 removeSummaries(node);
289
290 auto childFunc = cast<FunctionOpInterface>(node.getOperation());
291 if (failed(runActivityAnnotationsForFunction(childFunc, solver))) {
292 assert(false && "dataflow solver failed\n");
293 }
294
295 enzyme::PointsToSets childP2Sets(nullptr);
296 enzyme::ForwardOriginsMap childFwdOrigins(nullptr);
297 enzyme::BackwardOriginsMap childBwdOrigins(nullptr);
298 size_t numResults = childFunc.getResultTypes().size();
299 SmallVector<enzyme::ForwardOriginsLattice> returnOperandOrigins(
300 numResults, ForwardOriginsLattice(nullptr));
301 SmallVector<enzyme::AliasClassLattice> returnAliasClasses(
302 numResults, AliasClassLattice(nullptr));
303 computeSummaries(childFunc, solver, childP2Sets, childFwdOrigins,
304 childBwdOrigins, returnOperandOrigins, returnAliasClasses);
305 serializeSummaries(childFunc, childP2Sets, childFwdOrigins,
306 returnOperandOrigins, returnAliasClasses);
307 if (node.getOperation() == funcOp) {
308 (void)p2sets.join(childP2Sets);
309 (void)forwardOriginsMap.join(childFwdOrigins);
310 (void)backwardOriginsMap.meet(childBwdOrigins);
311 }
312 }
313}
314
315bool enzyme::DataFlowActivityAnalyzer::isOriginActive(OriginAttr origin) {
316 if (auto argOriginAttr = dyn_cast<ArgumentOriginAttr>(origin)) {
317 return llvm::is_contained(
319 argActivity[argOriginAttr.getArgNumber()]);
320 }
321 auto retOriginAttr = cast<ReturnOriginAttr>(origin);
322 return llvm::is_contained(
324 returnActivity[retOriginAttr.getReturnNumber()]);
325};
326
327void enzyme::DataFlowActivityAnalyzer::joinActiveDataState(
328 Value v, ForwardOriginsLattice &sources, BackwardOriginsLattice &sinks) {
329 (void)sources.join(*solver.getOrCreateState<ForwardOriginsLattice>(v));
330 (void)sinks.meet(*solver.getOrCreateState<BackwardOriginsLattice>(v));
331};
332
333void enzyme::DataFlowActivityAnalyzer::joinActivePointerState(
334 const AliasClassSet &aliasClasses, ForwardOriginsLattice &sources,
335 BackwardOriginsLattice &sinks) {
336 traversePointsToSets(aliasClasses, p2sets, [&](DistinctAttr aliasClass) {
337 (void)sources.merge(forwardOriginsMap.getOrigins(aliasClass));
338 (void)sinks.merge(backwardOriginsMap.getOrigins(aliasClass));
339 });
340};
341
342void enzyme::DataFlowActivityAnalyzer::joinActiveValueState(
343 Value v, ForwardOriginsLattice &sources, BackwardOriginsLattice &sinks) {
344 if (isa<LLVM::LLVMPointerType, MemRefType>(v.getType())) {
345 auto *aliasClasses = solver.getOrCreateState<AliasClassLattice>(v);
346 joinActivePointerState(aliasClasses->getAliasClassesObject(), sources,
347 sinks);
348 } else {
349 joinActiveDataState(v, sources, sinks);
350 }
351}
352
353std::optional<Value> getStored(Operation *op);
354
356 auto cached = inactiveOpCache.find(op);
357 if (cached != inactiveOpCache.end())
358 return cached->second;
359 // An operation is active if it propagates active data.
360 ForwardOriginsLattice sources(nullptr);
361 BackwardOriginsLattice sinks(nullptr);
362 if (isPure(op)) {
363 // A pure operation can only propagate active data via its results
364 for (OpResult result : op->getResults()) {
365 joinActiveDataState(result, sources, sinks);
366 }
367 } else {
368 // As a special case, storing an active pointer makes the operation active
369 // (otherwise Enzyme will ignore it). For example, `getelementptr
370 // %activeptr` is *inactive*, while `store %activeptr` is *active*.
371 if (hasEffect<MemoryEffects::Write>(op)) {
372 if (auto storedVal = getStored(op)) {
373 auto *storedClass =
374 solver.getOrCreateState<AliasClassLattice>(*storedVal);
375 joinActivePointerState(storedClass->getAliasClassesObject(), sources,
376 sinks);
377 }
378 } else if (auto callOp = dyn_cast<CallOpInterface>(op)) {
379 auto callable = cast<CallableOpInterface>(callOp.resolveCallable());
380 if (callable->hasAttr(
381 EnzymeDialect::getDenseActivityAnnotationAttrName())) {
382 for (Value operand : callOp.getArgOperands())
383 joinActiveValueState(operand, sources, sinks);
384 }
385 // TODO: We need to determine if the body of the function contains active
386 // instructions
387 } else if (op->hasTrait<OpTrait::HasRecursiveMemoryEffects>()) {
388 // An operation with recursive memory effects is active if it contains an
389 // active operation
390 for (Region &region : op->getRegions()) {
391 for (Operation &bodyOp : region.getOps()) {
392 if (!isInactiveOperation(&bodyOp)) {
393 return false;
394 }
395 }
396 }
397 }
398
399 // Default: the op is active iff any of its operands or results are
400 // active data.
401 for (Value operand : op->getOperands())
402 joinActiveDataState(operand, sources, sinks);
403 for (OpResult result : op->getResults())
404 joinActiveDataState(result, sources, sinks);
405 }
406
407 auto latticeIsActive = [this](const SparseSetLattice<OriginAttr> &lattice) {
408 if (lattice.isUnknown())
409 return true;
410 if (lattice.isUndefined())
411 return false;
412 return llvm::any_of(lattice.getElements(), [this](OriginAttr origin) {
413 return isOriginActive(origin);
414 });
415 };
416 bool activeOp = latticeIsActive(sources) && latticeIsActive(sinks);
417 bool result = !activeOp;
418 inactiveOpCache[op] = result;
419 return result;
420}
421
423 // Activity of function arguments is given by the user
424 if (auto blockArg = dyn_cast<BlockArgument>(value)) {
425 if (blockArg.getOwner() == &funcOp.getFunctionBody().front())
426 return argActivity[blockArg.getArgNumber()] == DIFFE_TYPE::CONSTANT;
427 }
428
429 auto cached = inactiveValueCache.find(value);
430 if (cached != inactiveValueCache.end())
431 return cached->second;
432
433 ForwardOriginsLattice sources(nullptr);
434 BackwardOriginsLattice sinks(nullptr);
435 joinActiveValueState(value, sources, sinks);
436
437 bool activeSource = false;
438 if (sources.isUnknown()) {
439 activeSource = true;
440 } else if (sources.isUndefined()) {
441 activeSource = false;
442 } else {
443 activeSource =
444 llvm::any_of(sources.getOrigins(), [this](OriginAttr origin) {
445 return isOriginActive(origin);
446 });
447 }
448 bool activeSink = false;
449 if (sinks.isUnknown()) {
450 activeSink = true;
451 } else if (sinks.isUndefined()) {
452 activeSink = false;
453 } else {
454 activeSink = llvm::any_of(sinks.getOrigins(), [this](OriginAttr origin) {
455 return isOriginActive(origin);
456 });
457 }
458 bool activeVal = activeSource && activeSink;
459 bool result = !activeVal;
460 inactiveValueCache[value] = result;
461 return result;
462}
463
464static Operation *getFunctionFromCall(CallOpInterface iface) {
465 auto symbol = dyn_cast<SymbolRefAttr>(iface.getCallableForCallee());
466 if (!symbol)
467 return nullptr;
468
469 return SymbolTable::lookupNearestSymbolFrom(iface.getOperation(), symbol);
470}
471
472constexpr bool EnzymePrintActivity = false;
473
474static bool isReadOnly(Operation *op) {
475 bool hasRecursiveEffects = op->hasTrait<OpTrait::HasRecursiveMemoryEffects>();
476 if (hasRecursiveEffects) {
477 for (Region &region : op->getRegions()) {
478 for (auto &block : region) {
479 for (auto &nestedOp : block)
480 if (!isReadOnly(&nestedOp))
481 return false;
482 }
483 }
484 return true;
485 }
486
487 // If the op has memory effects, try to characterize them to see if the op
488 // is trivially dead here.
489 if (auto effectInterface = dyn_cast<MemoryEffectOpInterface>(op)) {
490 // Check to see if this op either has no effects, or only allocates/reads
491 // memory.
492 SmallVector<MemoryEffects::EffectInstance, 1> effects;
493 effectInterface.getEffects(effects);
494 if (!llvm::all_of(effects, [](const MemoryEffects::EffectInstance &it) {
495 return isa<MemoryEffects::Read>(it.getEffect());
496 })) {
497 return false;
498 }
499 return true;
500 }
501 return false;
502}
503
505 auto find = readOnlyCache.find(val);
506 if (find != readOnlyCache.end()) {
507 return find->second;
508 }
509 auto res = ::isReadOnly(val);
510 readOnlyCache[val] = res;
511 return res;
512}
513
514/// Is the use of value val as an argument of call CI known to be inactive
515/// This tool can only be used when in DOWN mode
516bool mlir::enzyme::ActivityAnalyzer::isFunctionArgumentConstant(
517 CallOpInterface CI, Value val) {
518 assert(directions & DOWN);
519
520 if (CI->hasAttr("enzyme_inactive"))
521 return true;
522
523 Operation *F = getFunctionFromCall(CI);
524
525 // Indirect function calls may actively use the argument
526 if (F == nullptr)
527 return false;
528
529 if (F->hasAttr("enzyme_inactive")) {
530 return true;
531 }
532
533 StringRef Name = cast<SymbolOpInterface>(F).getName();
534
535 // TODO(PR #904): Revisit commented code. Main reason for commenting out code
536 // are lack of preprocessing (type analysis) and frontend info
537 // (TargetLibraryInformation).
538
539 // Allocations, deallocations, and c++ guards don't impact the activity
540 // of arguments
541 // if (isAllocationFunction(Name, TLI) || isDeallocationFunction(Name, TLI))
542 // return true;
543 if (Name == "posix_memalign")
544 return true;
545
546 std::string demangledName = llvm::demangle(Name.str());
547 auto dName = StringRef(demangledName);
548 for (auto FuncName : DemangledKnownInactiveFunctionsStartingWith) {
549 if (startsWith(dName, FuncName)) {
550 return true;
551 }
552 }
553 if (demangledName == Name.str()) {
554 // Either demangeling failed
555 // or they are equal but matching failed
556 // if (!startsWith(Name, "llvm."))
557 // llvm::errs() << "matching failed: " << Name.str() << " "
558 // << demangledName << "\n";
559 }
560
561 for (auto FuncName : KnownInactiveFunctionsStartingWith) {
562 if (startsWith(Name, FuncName)) {
563 return true;
564 }
565 }
566
567 for (auto FuncName : KnownInactiveFunctionsContains) {
568 if (Name.contains(FuncName)) {
569 return true;
570 }
571 }
572 if (KnownInactiveFunctions.count(Name.str())) {
573 return true;
574 }
575
576 if (MPIInactiveCommAllocators.find(Name.str()) !=
578 return true;
579 }
580
581 for (unsigned intrinsicID : constantIntrinsics) {
582 if (startsWith(Name, llvm::Intrinsic::getBaseName(intrinsicID)))
583 return true;
584 }
585
586 // TODO: this should be lifted to proper operations via the interface.
587 // /// Only the first argument (magnitude) of copysign is active
588 // if (F->getIntrinsicID() == Intrinsic::copysign &&
589 // CI->getArgOperand(0) != val) {
590 // return true;
591 // }
592
593 // if (F->getIntrinsicID() == Intrinsic::memcpy && CI->getArgOperand(0) != val
594 // &&
595 // CI->getArgOperand(1) != val)
596 // return true;
597 // if (F->getIntrinsicID() == Intrinsic::memmove &&
598 // CI->getArgOperand(0) != val && CI->getArgOperand(1) != val)
599 // return true;
600
601 // only the float arg input is potentially active
602 if (Name == "frexp" || Name == "frexpf" || Name == "frexpl") {
603 return val != CI.getArgOperands()[0];
604 }
605
606 // The relerr argument is inactive
607 if (Name == "Faddeeva_erf" || Name == "Faddeeva_erfc" ||
608 Name == "Faddeeva_erfcx" || Name == "Faddeeva_erfi" ||
609 Name == "Faddeeva_dawson") {
610 for (size_t i = 0; i < CI.getArgOperands().size() - 1; i++) {
611 if (val == CI.getArgOperands()[i])
612 return false;
613 }
614 return true;
615 }
616
617 // only the buffer is active for mpi send/recv
618 if (Name == "MPI_Recv" || Name == "MPI_Send" || Name == "PMPI_Recv" ||
619 Name == "PMPI_Send") {
620 return val != CI.getArgOperands()[0];
621 }
622 // only the recv buffer and request is active for mpi isend/irecv
623 if (Name == "MPI_Irecv" || Name == "MPI_Isend" || Name == "PMPI_Irecv" ||
624 Name == "PMPI_Isend") {
625 return val != CI.getArgOperands()[0] && val != CI.getArgOperands()[6];
626 }
627
628 // only request is active
629 if (Name == "MPI_Wait" || Name == "PMPI_Wait")
630 return val != CI.getArgOperands()[0];
631
632 if (Name == "MPI_Waitall" || Name == "PMPI_Waitall")
633 return val != CI.getArgOperands()[1];
634
635 // With all other options exhausted we have to assume this function could
636 // actively use the value
637 return false;
638}
639
640// TODO: better support for known function calls. Ideally, they should become
641// operations, but we also need parity with LLVM-enzyme.
642/// Call the function propagateFromOperand on all operands of CI
643/// that could impact the activity of the call instruction
644// static inline void propagateArgumentInformation(
645// /*TargetLibraryInfo &TLI,*/ CallOpInterface CI,
646// std::function<bool(Value)> propagateFromOperand) {
647
648// if (Operation *F = getFunctionFromCall(CI)) {
649// // These functions are known to only have the first argument impact
650// // the activity of the call instruction
651// StringRef Name = cast<SymbolOpInterface>(F).getName();
652// if (Name == "lgamma" || Name == "lgammaf" || Name == "lgammal" ||
653// Name == "lgamma_r" || Name == "lgammaf_r" || Name == "lgammal_r" ||
654// Name == "__lgamma_r_finite" || Name == "__lgammaf_r_finite" ||
655// Name == "__lgammal_r_finite") {
656
657// propagateFromOperand(CI.getArgOperands()[0]);
658// return;
659// }
660
661// // Allocations, deallocations, and c++ guards are fully inactive
662// // if (isAllocationFunction(Name, TLI) || isDeallocationFunction(Name,
663// TLI)
664// // ||
665// // Name == "__cxa_guard_acquire" || Name == "__cxa_guard_release" ||
666// // Name == "__cxa_guard_abort")
667// // return;
668
669// /// Only the first argument (magnitude) of copysign is active
670// if (Name == llvm::Intrinsic::getName(llvm::Intrinsic::copysign)) {
671// propagateFromOperand(CI.getArgOperands()[0]);
672// return;
673// }
674
675// // Certain intrinsics are inactive by definition
676// // and have nothing to propagate.
677// for (unsigned intrinsicID : constantIntrinsics) {
678// if (startsWith(Name, llvm::Intrinsic::getBaseName(intrinsicID)))
679// return;
680// }
681
682// if (startsWith(Name,
683// llvm::Intrinsic::getBaseName(llvm::Intrinsic::memcpy)) ||
684// startsWith(Name,
685// llvm::Intrinsic::getBaseName(llvm::Intrinsic::memmove))) {
686// propagateFromOperand(CI.getArgOperands()[0]);
687// propagateFromOperand(CI.getArgOperands()[1]);
688// return;
689// }
690
691// if (Name == "frexp" || Name == "frexpf" || Name == "frexpl") {
692// propagateFromOperand(CI.getArgOperands()[0]);
693// return;
694// }
695// if (Name == "Faddeeva_erf" || Name == "Faddeeva_erfc" ||
696// Name == "Faddeeva_erfcx" || Name == "Faddeeva_erfi" ||
697// Name == "Faddeeva_dawson") {
698// for (size_t i = 0; i < CI.getArgOperands().size() - 1; i++) {
699// propagateFromOperand(CI.getArgOperands()[i]);
700// }
701// return;
702// }
703
704// if (Name == "julia.call" || Name == "julia.call2") {
705// for (size_t i = 1; i < CI.getArgOperands().size(); i++) {
706// propagateFromOperand(CI.getArgOperands()[i]);
707// }
708// return;
709// }
710// }
711
712// // For other calls, check all operands of the operation
713// // as conservatively they may impact the activity of the call
714// for (Value a : CI->getOperands()) {
715// if (propagateFromOperand(a))
716// break;
717// }
718// }
719
720/// Return whether this operation is known not to propagate adjoints
721/// Note that operation could return an active pointer, but
722/// do not propagate adjoints themselves
724 Operation *I) {
725 // This analysis may only be called by instructions corresponding to
726 // the function analyzed by TypeInfo
727 assert(I);
728 // assert(TR.getFunction() == I->getParent()->getParent());
729
730 // The return instruction doesn't impact activity (handled specifically
731 // during adjoint generation)
733 return true;
734
735 if (auto ifaceOp = dyn_cast<enzyme::ActivityOpInterface>(I)) {
736 if (ifaceOp.isInactive()) {
737 return true;
738 }
739 }
740
741 // Branch, unreachable, and previously computed constants are inactive
742 if (/*|| isa<cf::BranchOp>(I)*/ ConstantOperations.contains(I)) {
743 return true;
744 }
745
746 /// Previously computed inactives remain inactive
747 if (ActiveOperations.contains(I)) {
748 return false;
749 }
750
751 if (notForAnalysis.count(I->getBlock())) {
753 llvm::errs() << " constant instruction as dominates unreachable " << *I
754 << "\n";
755 InsertConstantOperation(TR, I);
756 return true;
757 }
758
759 if (auto CI = dyn_cast<CallOpInterface>(I)) {
760 // TODO(PR #904): This needs to be put into the enzyme dialect
761 if (CI->hasAttr("enzyme_active")) {
763 llvm::errs() << "forced active " << *I << "\n";
764 ActiveOperations.insert(I);
765 return false;
766 }
767 if (CI->hasAttr("enzyme_inactive")) {
769 llvm::errs() << "forced inactive " << *I << "\n";
770 InsertConstantOperation(TR, I);
771 return true;
772 }
773 Operation *called = getFunctionFromCall(CI);
774
775 if (called) {
776 if (called->hasAttr("enzyme_active")) {
778 llvm::errs() << "forced active " << *I << "\n";
779 ActiveOperations.insert(I);
780 return false;
781 }
782 if (called->hasAttr("enzyme_inactive")) {
784 llvm::errs() << "forced inactive " << *I << "\n";
785 InsertConstantOperation(TR, I);
786 return true;
787 }
789 cast<SymbolOpInterface>(called).getName().str())) {
790 InsertConstantOperation(TR, I);
791 return true;
792 }
793 }
794 }
795
796 /// A store into all integral memory is inactive
797 // TODO: per-operation stuff
798 // if (auto SI = dyn_cast<StoreInst>(I)) {
799 // auto StoreSize = SI->getParent()
800 // ->getParent()
801 // ->getParent()
802 // ->getDataLayout()
803 // .getTypeSizeInBits(SI->getValueOperand()->getType())
804 // /
805 // 8;
806
807 // bool AllIntegral = true;
808 // bool SeenInteger = false;
809 // auto q = TR.query(SI->getPointerOperand()).Data0();
810 // for (int i = -1; i < (int)StoreSize; ++i) {
811 // auto dt = q[{i}];
812 // if (dt.isIntegral() || dt == BaseType::Anything) {
813 // SeenInteger = true;
814 // if (i == -1)
815 // break;
816 // } else if (dt.isKnown()) {
817 // AllIntegral = false;
818 // break;
819 // }
820 // }
821
822 // if (AllIntegral && SeenInteger) {
823 // if (EnzymePrintActivity)
824 // llvm::errs() << " constant instruction from TA " << *I << "\n";
825 // InsertConstantInstruction(TR, I);
826 // return true;
827 // }
828 // }
829 // if (auto SI = dyn_cast<AtomicRMWInst>(I)) {
830 // auto StoreSize = SI->getParent()
831 // ->getParent()
832 // ->getParent()
833 // ->getDataLayout()
834 // .getTypeSizeInBits(I->getType()) /
835 // 8;
836
837 // bool AllIntegral = true;
838 // bool SeenInteger = false;
839 // auto q = TR.query(SI->getOperand(0)).Data0();
840 // for (int i = -1; i < (int)StoreSize; ++i) {
841 // auto dt = q[{i}];
842 // if (dt.isIntegral() || dt == BaseType::Anything) {
843 // SeenInteger = true;
844 // if (i == -1)
845 // break;
846 // } else if (dt.isKnown()) {
847 // AllIntegral = false;
848 // break;
849 // }
850 // }
851
852 // if (AllIntegral && SeenInteger) {
853 // if (EnzymePrintActivity)
854 // llvm::errs() << " constant instruction from TA " << *I << "\n";
855 // InsertConstantInstruction(TR, I);
856 // return true;
857 // }
858 // }
859
860 // if (EnzymePrintActivity)
861 // llvm::errs() << "checking if is constant[" << (int)directions << "] " <<
862 // *I
863 // << "\n";
864
865 // if (auto II = dyn_cast<IntrinsicInst>(I)) {
866 // switch (II->getIntrinsicID()) {
867 // case Intrinsic::nvvm_barrier0:
868 // case Intrinsic::nvvm_barrier0_popc:
869 // case Intrinsic::nvvm_barrier0_and:
870 // case Intrinsic::nvvm_barrier0_or:
871 // case Intrinsic::nvvm_membar_cta:
872 // case Intrinsic::nvvm_membar_gl:
873 // case Intrinsic::nvvm_membar_sys:
874 // case Intrinsic::amdgcn_s_barrier:
875 // case Intrinsic::assume:
876 // case Intrinsic::stacksave:
877 // case Intrinsic::stackrestore:
878 // case Intrinsic::lifetime_start:
879 // case Intrinsic::lifetime_end:
880 // case Intrinsic::dbg_addr:
881 // case Intrinsic::dbg_declare:
882 // case Intrinsic::dbg_value:
883 // case Intrinsic::invariant_start:
884 // case Intrinsic::invariant_end:
885 // case Intrinsic::var_annotation:
886 // case Intrinsic::ptr_annotation:
887 // case Intrinsic::annotation:
888 // case Intrinsic::codeview_annotation:
889 // case Intrinsic::expect:
890 // case Intrinsic::type_test:
891 // case Intrinsic::donothing:
892 // case Intrinsic::prefetch:
893 // case Intrinsic::trap:
894 // case Intrinsic::is_constant:
895 // case Intrinsic::memset:
896 // if (EnzymePrintActivity)
897 // llvm::errs() << "known inactive intrinsic " << *I << "\n";
898 // InsertConstantInstruction(TR, I);
899 // return true;
900
901 // default:
902 // break;
903 // }
904 // }
905
906 // Analyzer for inductive assumption where we attempt to prove this is
907 // inactive from a lack of active users
908 std::shared_ptr<mlir::enzyme::ActivityAnalyzer> DownHypothesis;
909
910 // If this instruction does not write to memory that outlives itself
911 // (potentially propagating derivative information), the only way to propagate
912 // derivative information is through the return value
913 // TODO the "doesn't write to active memory" can be made more aggressive than
914 // doesn't write to any memory
915 bool noActiveWrite = false;
916
917 if (isReadOnly(I))
918 noActiveWrite = true;
919 else if (auto CI = dyn_cast<CallOpInterface>(I)) {
920 // if (AA.onlyReadsMemory(CI)) {
921 // noActiveWrite = true;
922 // } else
923 if (Operation *F = getFunctionFromCall(CI)) {
924 // if (isMemFreeLibMFunction(F->getName())) {
925 // noActiveWrite = true;
926 // } else
927 StringRef Name = cast<SymbolOpInterface>(F).getName();
928 if (Name == "frexp" || Name == "frexpf" || Name == "frexpl") {
929 noActiveWrite = true;
930 }
931 }
932 }
933 if (noActiveWrite) {
934 // Even if returning a pointer, this instruction is considered inactive
935 // since the instruction doesn't prop gradients. Thus, so long as we don't
936 // return an object containing a float, this instruction is inactive
937 // if (!TR.intType(1, I, /*errifNotFound*/ false).isPossibleFloat()) {
938 // if (EnzymePrintActivity)
939 // llvm::errs()
940 // << " constant instruction from known non-float non-writing "
941 // "instruction "
942 // << *I << "\n";
943 // InsertConstantInstruction(TR, I);
944 // return true;
945 // }
946
947 // If all returned values constant otherwise, the operation is inactive
948 if (llvm::all_of(I->getResults(),
949 [&](Value v) { return isConstantValue(TR, v); })) {
951 llvm::errs() << " constant instruction from known constant non-writing "
952 "instruction "
953 << *I << "\n";
954 InsertConstantOperation(TR, I);
955 return true;
956 }
957
958 // Even if the return is nonconstant, it's worth checking explicitly the
959 // users since unlike isConstantValue, returning a pointer does not make the
960 // instruction active
961 if (directions & DOWN) {
962 // We shall now induct on this instruction being inactive and try to prove
963 // this fact from a lack of active users.
964
965 // If we aren't a phi node (and thus potentially recursive on uses) and
966 // already equal to the current direction, we don't need to induct,
967 // reducing runtime.
968 if (directions == DOWN /*&& !isa<PHINode>(I)*/) {
969 // TODO(PR #904): A lot of this code is written assuming LLVM IR object
970 // inheritance (phi nodes are instructions, instructions are values).
971 // More careful handling is necessary for MLIR block arguments, but this
972 // could simplify the code as a result.
973 if (llvm::all_of(I->getResults(), [&](Value val) {
974 return isValueInactiveFromUsers(TR, val, UseActivity::None);
975 })) {
977 llvm::errs() << " constant instruction[" << (int)directions
978 << "] from users instruction " << *I << "\n";
979 InsertConstantOperation(TR, I);
980 return true;
981 }
982 } else {
983 DownHypothesis = std::shared_ptr<mlir::enzyme::ActivityAnalyzer>(
984 new mlir::enzyme::ActivityAnalyzer(*this, DOWN));
985 DownHypothesis->ConstantOperations.insert(I);
986 if (llvm::all_of(I->getResults(), [&](Value val) {
987 return DownHypothesis->isValueInactiveFromUsers(
988 TR, val, UseActivity::None);
989 })) {
991 llvm::errs() << " constant instruction[" << (int)directions
992 << "] from users instruction " << *I << "\n";
993 InsertConstantOperation(TR, I);
994 insertConstantsFrom(TR, *DownHypothesis);
995 return true;
996 }
997 }
998 }
999 }
1000
1001 std::shared_ptr<mlir::enzyme::ActivityAnalyzer> UpHypothesis;
1002 if (directions & UP) {
1003 // If this instruction has no active operands, the instruction
1004 // is inactive.
1005 // TODO This isn't 100% accurate and will incorrectly mark a no-argument
1006 // function that reads from active memory as constant
1007 // Technically the additional constraint is that this does not read from
1008 // active memory, where we have assumed that the only active memory
1009 // we care about is accessible from arguments passed (and thus not globals)
1010 UpHypothesis = std::shared_ptr<mlir::enzyme::ActivityAnalyzer>(
1011 new mlir::enzyme::ActivityAnalyzer(*this, UP));
1012 UpHypothesis->ConstantOperations.insert(I);
1013 assert(directions & UP);
1014 SmallPtrSet<Value, 4> toredo;
1015 if (UpHypothesis->isOperationInactiveFromOrigin(TR, I, std::nullopt,
1016 &toredo)) {
1018 llvm::errs() << " constant instruction from origin "
1019 "instruction "
1020 << *I << "\n";
1021 InsertConstantOperation(TR, I);
1022 insertConstantsFrom(TR, *UpHypothesis);
1023 if (DownHypothesis)
1024 insertConstantsFrom(TR, *DownHypothesis);
1025 return true;
1026 } else if (directions == (UP | DOWN)) {
1027 for (Value operand : toredo) {
1028 ReEvaluateOpIfInactiveValue[operand].insert(I);
1029 }
1030 }
1031 }
1032
1033 // Otherwise we must fall back and assume this instruction to be active.
1034 ActiveOperations.insert(I);
1036 llvm::errs() << "couldnt decide fallback as nonconstant instruction("
1037 << (int)directions << "):" << *I << "\n";
1038 if (noActiveWrite && (directions == (UP | DOWN)))
1039 for (Value result : I->getResults())
1040 ReEvaluateOpIfInactiveValue[result].insert(I);
1041 return false;
1042}
1043
1044static bool isFunctionReturn(Operation *op) {
1046 return false;
1047 return dyn_cast<FunctionOpInterface>(op->getParentOp());
1048}
1049
1050static bool isValuePotentiallyUsedAsPointer(Value val) {
1051 std::deque<Value> todo = {val};
1052 SmallPtrSet<Value, 3> seen;
1053 while (todo.size()) {
1054 auto cur = todo.back();
1055 todo.pop_back();
1056 if (seen.count(cur))
1057 continue;
1058 seen.insert(cur);
1059 for (Operation *user : cur.getUsers()) {
1060 if (auto regionIface =
1061 dyn_cast<RegionBranchOpInterface>(user->getParentOp()))
1062 if (auto termIface =
1063 dyn_cast<RegionBranchTerminatorOpInterface>(user)) {
1064 SmallVector<RegionSuccessor> successors;
1065 termIface.getSuccessorRegions(
1066 SmallVector<Attribute>(termIface->getNumOperands(), Attribute()),
1067 successors);
1068
1069 auto parentOp = termIface->getParentOp();
1070 for (auto &successor : successors) {
1071 OperandRange operandRange =
1072 termIface.getSuccessorOperands(successor);
1073 ValueRange targetValues =
1074 successor.isOperation()
1075 ? parentOp->getResults()
1076 : regionIface.getSuccessorInputs(successor);
1077 assert(operandRange.size() == targetValues.size());
1078 for (auto &&[prev, post] : llvm::zip(operandRange, targetValues)) {
1079 if (prev == cur) {
1080 todo.push_back(post);
1081 }
1082 }
1083 }
1084 continue;
1085 }
1086 if (auto iface = dyn_cast<BranchOpInterface>(user)) {
1087 for (auto &op : user->getOpOperands())
1088 if (op.get() == cur)
1089 if (auto blk =
1090 iface.getSuccessorBlockArgument(op.getOperandNumber()))
1091 todo.push_back(*blk);
1092 continue;
1093 }
1094 if (isFunctionReturn(user))
1095 return true;
1096 // The operation is known not to read or write memory.
1097 if (isa<MemoryEffectOpInterface>(user) &&
1098 !hasEffect<MemoryEffects::Read>(user) &&
1099 !hasEffect<MemoryEffects::Write>(user)) {
1100 for (Value result : user->getResults()) {
1101 todo.push_back(result);
1102 }
1103 continue;
1104 }
1106 llvm::errs() << " VALUE potentially used as pointer " << val << " by "
1107 << *user << "\n";
1108 return true;
1109 }
1110 }
1111 return false;
1112}
1113
1114static bool mayAllocateMemory(Operation *op) {
1115 if (isa<MemoryEffectOpInterface>(op))
1116 return hasEffect<MemoryEffects::Allocate>(op);
1117 return true;
1118}
1119
1120static bool mayReadFromMemory(Operation *op) {
1121 if (isa<MemoryEffectOpInterface>(op))
1122 return hasEffect<MemoryEffects::Read>(op);
1123 return true;
1124}
1125
1126static bool mayWriteToMemory(Operation *op) {
1127 if (isa<MemoryEffectOpInterface>(op))
1128 return hasEffect<MemoryEffects::Write>(op);
1129 return true;
1130}
1131
1132static FunctionOpInterface getFunctionIfArgument(Value value) {
1133 auto arg = dyn_cast<BlockArgument>(value);
1134 if (!arg)
1135 return nullptr;
1136
1137 Block *block = arg.getOwner();
1138 if (!block->isEntryBlock())
1139 return nullptr;
1140
1141 return dyn_cast<FunctionOpInterface>(block->getParentOp());
1142}
1143
1144// For a given instruction, determine whether it is a terminator which
1145// controls dataflow out, and if so return all users either in results
1146// or blockarguments
1147static std::optional<SmallVector<Value>>
1148getPotentialTerminatorUsers(Operation *op, Value parent) {
1149 auto block = op->getBlock();
1150
1151 if (block->getTerminator() != op)
1152 return {};
1153 if (isFunctionReturn(op))
1154 return {};
1155
1156 if (auto termIface = dyn_cast<ADDataFlowOpInterface>(op->getParentOp())) {
1157 return termIface.getPotentialTerminatorUsers(op, parent);
1158 } else if (auto regionIface =
1159 dyn_cast<RegionBranchOpInterface>(op->getParentOp())) {
1160 if (auto termIface = dyn_cast<RegionBranchTerminatorOpInterface>(op)) {
1161 SmallVector<RegionSuccessor> successors;
1162 termIface.getSuccessorRegions(
1163 SmallVector<Attribute>(termIface->getNumOperands(), Attribute()),
1164 successors);
1165
1166 auto parentOp = termIface->getParentOp();
1167 SmallVector<Value> results;
1168 for (auto &successor : successors) {
1169 OperandRange operandRange = termIface.getSuccessorOperands(successor);
1170 ValueRange targetValues =
1171 successor.isOperation() ? parentOp->getResults()
1172 : regionIface.getSuccessorInputs(successor);
1173 assert(operandRange.size() == targetValues.size());
1174 for (auto &&[prev, post] : llvm::zip(operandRange, targetValues)) {
1175 if (prev == parent) {
1176 results.push_back(post);
1177 }
1178 }
1179 }
1180 return std::move(results);
1181 }
1182 }
1183 SmallVector<Value> results;
1184 if (auto iface = dyn_cast<BranchOpInterface>(op)) {
1185 for (auto &operand : op->getOpOperands())
1186 if (operand.get() == parent)
1187 if (auto blk =
1188 iface.getSuccessorBlockArgument(operand.getOperandNumber())) {
1189 results.push_back(*blk);
1190 return std::move(results);
1191 }
1192 }
1193
1194 // assume all terminator operands potentially flow into all op results
1195 for (auto res : op->getParentOp()->getResults())
1196 results.push_back(res);
1197
1198 // assume all terminator operands potentially flow into all blockArgs in
1199 // region
1200 for (auto &blk : *block->getParent())
1201 for (auto arg : blk.getArguments())
1202 results.push_back(arg);
1203
1204 // assume all terminator operands potentially flow into all other region
1205 // entries
1206 for (auto &reg : op->getParentOp()->getRegions())
1207 for (auto arg : reg.front().getArguments())
1208 results.push_back(arg);
1209
1210 return std::move(results);
1211}
1212
1213// For a result of an op, find all values which could flow into this result
1214static SmallVector<Value> getPotentialIncomingValues(OpResult res) {
1215 Operation *owner = res.getOwner();
1216 SmallVector<Value> potentialSources;
1217
1218 auto resultNo = res.getResultNumber();
1219
1220 if (auto iface = dyn_cast<ADDataFlowOpInterface>(owner)) {
1221 for (auto val : iface.getPotentialIncomingValuesRes(res))
1222 potentialSources.push_back(val);
1223 return potentialSources;
1224 } else if (auto iface = dyn_cast<RegionBranchOpInterface>(owner)) {
1225 SmallVector<RegionSuccessor> successors;
1226 iface.getSuccessorRegions(RegionBranchPoint::parent(), successors);
1227 for (auto &succ : successors) {
1228 if (!succ.isOperation())
1229 continue;
1230 auto successorOperands =
1231 llvm::to_vector(iface.getEntrySuccessorOperands(succ));
1232
1233 if (successorOperands.size() != owner->getNumResults()) {
1234 llvm::errs() << *owner << "\n";
1235 }
1236 assert(successorOperands.size() == owner->getNumResults() &&
1237 "expected all results to be populated with incoming operands");
1238
1239 potentialSources.push_back(successorOperands[resultNo]);
1240 }
1241 } else {
1242 // assume all inputs potentially flow into all op results
1243 for (auto operand : owner->getOperands()) {
1244 potentialSources.push_back(operand);
1245 }
1246 }
1247
1248 for (Region &region : owner->getRegions()) {
1249 for (Block &block : region) {
1250 // TODO: MLIR blocks without terminator?
1251 if (auto iface = dyn_cast<RegionBranchTerminatorOpInterface>(
1252 block.getTerminator())) {
1253 // TODO: the interface may also tell us which regions are allowed to
1254 // yield parent op results, and which only branch to other regions.
1255 auto successorOperands =
1256 llvm::to_vector(iface.getSuccessorOperands(RegionSuccessor(owner)));
1257 // TODO: understand/document the assumption of how operands flow.
1258
1259 if (successorOperands.size() != owner->getNumResults()) {
1260 llvm::errs() << *owner << "\n";
1261 }
1262 assert(successorOperands.size() == owner->getNumResults() &&
1263 "expected all results to be populated with yielded "
1264 "terminator operands");
1265 potentialSources.push_back(successorOperands[resultNo]);
1266 } else {
1267 // assume all terminator operands potentially flow into op results
1268 for (Value v : block.getTerminator()->getOperands())
1269 potentialSources.push_back(v);
1270 }
1271 }
1272 }
1273
1274 return potentialSources;
1275}
1277// For a blockargument, find all non-operand values which could flow into
1278// this result
1279static SmallVector<Value> getPotentialIncomingValues(BlockArgument arg) {
1280 SetVector<Value> potentialSources;
1281
1282 if (!arg.getOwner()->isEntryBlock()) {
1283 for (Block *predecessor : arg.getOwner()->getPredecessors()) {
1284 Operation *terminator = predecessor->getTerminator();
1285 if (auto iface = dyn_cast<BranchOpInterface>(terminator)) {
1286 for (const auto &en : llvm::enumerate(predecessor->getSuccessors())) {
1287 if (en.value() != arg.getOwner())
1288 continue;
1289
1290 Value inflow = iface.getSuccessorOperands(en.index())
1291 .getForwardedOperands()[arg.getArgNumber()];
1292 potentialSources.insert(inflow);
1293 }
1294 } else {
1295 for (Value operand : terminator->getOperands())
1296 potentialSources.insert(operand);
1297 }
1298 }
1299 return potentialSources.takeVector();
1300 }
1301
1302 Operation *parent = arg.getOwner()->getParentOp();
1303 Region *parentRegion = arg.getOwner()->getParent();
1304 // Use region interface to find the values flowing into the entry block.
1305 if (auto iface = dyn_cast<ADDataFlowOpInterface>(parent)) {
1306 for (auto val : iface.getPotentialIncomingValuesArg(arg))
1307 potentialSources.insert(val);
1308 return potentialSources.takeVector();
1309 } else if (auto iface = dyn_cast<RegionBranchOpInterface>(parent)) {
1310 auto isRegionSucessorOf = [arg](RegionBranchOpInterface iface,
1311 Region *region,
1312 RegionBranchPoint predecessor,
1313 SetVector<Value> &potentialSources) {
1314 SmallVector<RegionSuccessor> successors;
1315 iface.getSuccessorRegions(predecessor, successors);
1316 for (const RegionSuccessor &successor : successors) {
1317 if (successor.getSuccessor() != region)
1318 continue;
1319
1320 unsigned operandOffset = static_cast<unsigned>(-1);
1321 for (const auto &en :
1322 llvm::enumerate(iface.getSuccessorInputs(successor))) {
1323 if (en.value() != arg)
1324 continue;
1325 operandOffset = en.index();
1326 }
1327 assert(operandOffset != static_cast<unsigned>(-1) &&
1328 "could not locate the position of the argument in the "
1329 "successor input list");
1330
1331 // Find the values that are forwarded to entry block arguments of
1332 // the current region.
1333 if (predecessor.isParent()) {
1334 // XXX: this assumes a contiguous slice of operands is mapped 1-1
1335 // without swaps to a contiguous slice of entry block arguments.
1336 assert(iface.getEntrySuccessorOperands(region).size() ==
1337 iface.getSuccessorInputs(successor).size());
1338 potentialSources.insert(
1339 iface.getEntrySuccessorOperands(region)[operandOffset]);
1340 } else {
1341 // Find all block terminators in the predecessor region that
1342 // may be branching to this region, and get the operands they
1343 // forward.
1344 for (Block &block : *predecessor.getTerminatorPredecessorOrNull()
1345 ->getParentRegion()) {
1346 // TODO: MLIR block without terminator
1347 if (auto terminator = dyn_cast<RegionBranchTerminatorOpInterface>(
1348 block.getTerminator())) {
1349 // XXX: this assumes a contiguous slice of operands is mapped
1350 // 1-1 without swaps to a contiguous slice of entry block
1351 // arguments.
1352 assert(terminator.getSuccessorOperands(region).size() ==
1353 iface.getSuccessorInputs(successor).size());
1354 potentialSources.insert(
1355 terminator.getSuccessorOperands(region)[operandOffset]);
1356 } else {
1357 for (Value v : block.getTerminator()->getOperands())
1358 potentialSources.insert(v);
1359 }
1360 }
1361 }
1362 }
1363 };
1364
1365 // Find all possible source regions for the current region.
1366 isRegionSucessorOf(iface, parentRegion, RegionBranchPoint::parent(),
1367 potentialSources);
1368 for (Region &childRegion : parent->getRegions())
1369 isRegionSucessorOf(iface, parentRegion,
1370 cast<RegionBranchTerminatorOpInterface>(
1371 childRegion.front().getTerminator()),
1372 potentialSources);
1373
1374 } else {
1375 // Conservatively assume any op operand and any terminator operand of
1376 // any region can flow into any block argument.
1377 for (Region &region : parent->getRegions()) {
1378 for (Block &block : region) {
1379 // TODO: MLIR blocks without terminator?
1380 for (Value v : block.getTerminator()->getOperands())
1381 potentialSources.insert(v);
1382 }
1383 }
1384
1385 // and also any operand to the parent
1386 for (auto op : parent->getOperands())
1387 potentialSources.insert(op);
1388 }
1389
1390 return potentialSources.takeVector();
1391}
1392
1393/// Return an ancestor of `op` that resides in the given region or nullptr if
1394/// there is no such ancestor.
1395static Operation *getAncestorInRegion(Operation *op, Region *region) {
1396 while (op && op->getParentRegion() != region) {
1397 op = op->getParentOp();
1398 }
1399 return op;
1400}
1401
1402/// Call `f` on all operations that may be executed after op.
1403static void allFollowersOf(Operation *op,
1404 function_ref<WalkResult(Operation *)> f) {
1405 auto regionIface = dyn_cast_or_null<RegionKindInterface>(op->getParentOp());
1406 (void)regionIface;
1407 assert((!regionIface ||
1408 regionIface.getRegionKind(op->getParentRegion()->getRegionNumber()) ==
1409 RegionKind::SSACFG) &&
1410 "graph regions not supported yet");
1411
1412 // Push back into `todo` the entry blocks of the successor regions of the
1413 // region with the given number that is attached to `op`. Leverage the
1414 // `RegionBranchOpInterface` when `op` implements it and assume all regions
1415 // may be successors otherwise.
1416 auto addEntryBlocksOfSuccessorRegions =
1417 [](Operation *op, RegionBranchPoint regionBranchPoint,
1418 std::deque<Block *> &todo) {
1419 if (auto iface = dyn_cast<RegionBranchOpInterface>(op)) {
1420 SmallVector<RegionSuccessor> regionSuccessors;
1421 iface.getSuccessorRegions(regionBranchPoint, regionSuccessors);
1422 for (const RegionSuccessor &rs : regionSuccessors) {
1423 if (!rs.isOperation() && !rs.getSuccessor()->empty())
1424 todo.push_back(&rs.getSuccessor()->front());
1425 }
1426 } else {
1427 for (Region &region : op->getRegions()) {
1428 if (region.empty())
1429 continue;
1430 todo.push_back(&region.front());
1431 }
1432 }
1433 };
1434
1435 // Push back into `todo` the blocks into which the control flow may be
1436 // transferred from the given operation.
1437 auto addOperationSuccessors = [addEntryBlocksOfSuccessorRegions](
1438 Operation *op, std::deque<Block *> &todo,
1439 bool skipNested) {
1440 // 1. If op has regions, consider control flow into those.
1441 if (op->getNumRegions() != 0 && !skipNested)
1442 addEntryBlocksOfSuccessorRegions(op, RegionBranchPoint::parent(), todo);
1443
1444 // 2. If op is a terminator, consider control flow from it.
1445 if (!op->mightHaveTrait<OpTrait::IsTerminator>() ||
1446 op != &op->getBlock()->back())
1447 return;
1448
1449 // 2a. Get the successors within the same region.
1450 Block *current = op->getBlock();
1451 for (Block *successor : current->getSuccessors()) {
1452 todo.push_back(successor);
1453 }
1454
1455 // 2b. If this is a region terminator op (XXX: we assume only the
1456 // terminators without successors can terminate regions), its successors may
1457 // be entry blocks of other regions (or the operation following the parent
1458 // op, but that is handled separately).
1459 if (current->getNumSuccessors() != 0)
1460 return;
1461
1462 // Do nothing when we hit a function (AD unit) or the end of the region
1463 // structure.
1464 Operation *parentOp = current->getParentOp();
1465 if (!parentOp || isa<FunctionOpInterface>(parentOp))
1466 return;
1467
1468 addEntryBlocksOfSuccessorRegions(
1469 parentOp,
1470 cast<RegionBranchTerminatorOpInterface>(
1471 current->getParent()->front().getTerminator()),
1472 todo);
1473 };
1474
1475 std::deque<Block *> todo;
1476 for (Operation *next = op->getNextNode(); next != nullptr;
1477 next = next->getNextNode()) {
1478 WalkResult r = f(next);
1479 if (r.wasInterrupted())
1480 return;
1481 addOperationSuccessors(next, todo, r.wasSkipped());
1482 }
1483
1484 llvm::SmallPtrSet<Block *, 8> done;
1485 while (!todo.empty()) {
1486 Block *block = todo.front();
1487 todo.pop_front();
1488 if (done.contains(block))
1489 continue;
1490 done.insert(block);
1491 for (Operation &nested : *block) {
1492 WalkResult r = f(&nested);
1493 if (r.wasInterrupted())
1494 return;
1495 addOperationSuccessors(&nested, todo, r.wasSkipped());
1496 if (getAncestorInRegion(op, nested.getParentRegion()) == &nested)
1497 break;
1498 }
1499 }
1500}
1501
1503 Value Val) {
1504 // This analysis may only be called by instructions corresponding to
1505 // the function analyzed by TypeInfo -- however if the Value
1506 // was created outside a function (e.g. global, constant), that is allowed
1507 assert(Val);
1508 // if (auto I = dyn_cast<Instruction>(Val)) {
1509 // if (TR.getFunction() != I->getParent()->getParent()) {
1510 // llvm::errs() << *TR.getFunction() << "\n";
1511 // llvm::errs() << *I << "\n";
1512 // }
1513 // assert(TR.getFunction() == I->getParent()->getParent());
1514 // }
1515 // if (auto Arg = dyn_cast<Argument>(Val)) {
1516 // assert(TR.getFunction() == Arg->getParent());
1517 // }
1518
1519 // Void values are definitionally inactive
1520 if (isa<LLVM::LLVMVoidType>(Val.getType()))
1521 return true;
1522
1523 // Token values are definitionally inactive
1524 if (isa<mlir::TokenType>(Val.getType()))
1525 return true;
1526
1527 /// If we've already shown this value to be inactive
1528 if (ConstantValues.find(Val) != ConstantValues.end()) {
1529 return true;
1530 }
1531
1532 /// If we've already shown this value to be active
1533 if (ActiveValues.find(Val) != ActiveValues.end()) {
1534 return false;
1535 }
1536
1537 // TODO: LLVM global initializers with regions?
1538 if (matchPattern(Val, m_Constant()))
1539 return true;
1540
1541 if (Operation *definingOp = Val.getDefiningOp()) {
1542 if (auto ifaceOp = dyn_cast<enzyme::ActivityOpInterface>(definingOp)) {
1543 if (ifaceOp.isInactive()) {
1544 return true;
1545 }
1546 }
1547 }
1548
1549 if (auto arg = dyn_cast<BlockArgument>(Val)) {
1550 // All arguments must be marked constant/nonconstant ahead of time
1551 if (auto funcIface = dyn_cast_or_null<FunctionOpInterface>(
1552 arg.getParentBlock()->getParentOp()))
1553 if (funcIface && arg.getOwner()->isEntryBlock() &&
1554 !funcIface.getArgAttr(arg.getArgNumber(),
1555 LLVM::LLVMDialect::getByValAttrName())) {
1556 llvm::errs() << funcIface << "\n";
1557 llvm::errs() << Val << "\n";
1558 assert(0 && "must've put arguments in constant/nonconstant");
1559 }
1560 // if (!funcIface || !arg.getOwner()->isEntryBlock()) {
1561 // TODO: we want a more advanced analysis based on MLIR interfaces here
1562 // For now, conservatively assume all block arguments are active
1563 // return false;
1564
1565 //
1566 // The code below is incomplete. We want to check all predecessors and,
1567 // additionally, if the owner block is listed as as successor of a given
1568 // predecessor more than once. Only if the value is constant in all cases
1569 // should it be deemed constant here. Some mixed evaluation using dataflow
1570 // analysis for constant propagation (similar to SCCP) and for activity
1571 // analysis may be more precise.
1572 //
1573 // for (Block *predecessor : arg.getOwner()->getPredecessors()) {
1574 // if (auto branch =
1575 // dyn_cast<BranchOpInterface>(predecessor->getTerminator())) {
1576 // auto it = llvm::find(predecessor->getSuccessors(), arg.getOwner());
1577 // unsigned successorNo = std::distance(predecessor->succ_begin(),
1578 // it); SuccessorOperands successorOperands =
1579 // branch.getSuccessorOperands(successorNo);
1580 // // If the argument is forwarded, this will be non-empty.
1581 // if (Value passedOperand = successorOperands[arg.getArgNumber()]) {
1582 // // TODO: we must avoid infinite recursion here...
1583 // return isConstantValue(TR, passedOperand);
1584 // }
1585 // }
1586 // }
1587 // }
1588 }
1589
1590 // This value is certainly an integer (and only and integer, not a pointer or
1591 // float). Therefore its value is constant
1592 if (TR.intType(1, Val, /*errIfNotFound*/ false).isIntegral()) {
1594 llvm::errs() << " Value const as integral " << (int)directions << " "
1595 << Val << " "
1596 << TR.intType(1, Val, /*errIfNotFound*/ false).str() << "\n";
1597 InsertConstantValue(TR, Val);
1598 return true;
1599 }
1600
1601#if 0
1602 // This value is certainly a pointer to an integer (and only and integer, not
1603 // a pointer or float). Therefore its value is constant
1604 // TODO use typeInfo for more aggressive activity analysis
1605 if (val->getType()->isPointerTy() &&
1606 cast<PointerType>(val->getType())->isIntOrIntVectorTy() &&
1607 TR.firstPointer(1, val, /*errifnotfound*/ false).isIntegral()) {
1609 llvm::errs() << " Value const as integral pointer" << (int)directions
1610 << " " << *val << "\n";
1611 InsertConstantValue(TR, Val);
1612 return true;
1613 }
1614#endif
1615
1616 // TODO: since in MLIR globals are operations like others, this should be
1617 // handled via interfaces.
1618 //
1619 // if (auto GI = dyn_cast<GlobalVariable>(Val)) {
1620 // // If operating under the assumption globals are inactive unless
1621 // // explicitly marked as active, this is inactive
1622 // if (!hasMetadata(GI, "enzyme_shadow") && EnzymeNonmarkedGlobalsInactive)
1623 // {
1624 // InsertConstantValue(TR, Val);
1625 // return true;
1626 // }
1627
1628 // if (GI->getName().contains("enzyme_const") ||
1629 // InactiveGlobals.count(GI->getName().str())) {
1630 // InsertConstantValue(TR, Val);
1631 // return true;
1632 // }
1633
1634 // // If this global is unchanging and the internal constant data
1635 // // is inactive, the global is inactive
1636 // if (GI->isConstant() && GI->hasInitializer() &&
1637 // isConstantValue(TR, GI->getInitializer())) {
1638 // InsertConstantValue(TR, Val);
1639 // if (EnzymePrintActivity)
1640 // llvm::errs() << " VALUE const global " << *Val
1641 // << " init: " << *GI->getInitializer() << "\n";
1642 // return true;
1643 // }
1644
1645 // // If this global is a pointer to an integer, it is inactive
1646 // // TODO note this may need updating to consider the size
1647 // // of the global
1648 // auto res = TR.query(GI).Data0();
1649 // auto dt = res[{-1}];
1650 // if (dt.isIntegral()) {
1651 // if (EnzymePrintActivity)
1652 // llvm::errs() << " VALUE const as global int pointer " << *Val
1653 // << " type - " << res.str() << "\n";
1654 // InsertConstantValue(TR, Val);
1655 // return true;
1656 // }
1657
1658 // // If this is a global local to this translation unit with inactive
1659 // // initializer and no active uses, it is definitionally inactive
1660 // bool usedJustInThisModule =
1661 // GI->hasInternalLinkage() || GI->hasPrivateLinkage();
1662
1663 // if (EnzymePrintActivity)
1664 // llvm::errs() << "pre attempting(" << (int)directions
1665 // << ") just used in module for: " << *GI << " dir"
1666 // << (int)directions << " justusedin:" <<
1667 // usedJustInThisModule
1668 // << "\n";
1669
1670 // if (directions == 3 && usedJustInThisModule) {
1671 // // TODO this assumes global initializer cannot refer to itself (lest
1672 // // infinite loop)
1673 // if (!GI->hasInitializer() || isConstantValue(TR, GI->getInitializer()))
1674 // {
1675
1676 // if (EnzymePrintActivity)
1677 // llvm::errs() << "attempting just used in module for: " << *GI <<
1678 // "\n";
1679 // // Not looking at users to prove inactive (definition of down)
1680 // // If all users are inactive, this is therefore inactive.
1681 // // Since we won't look at origins to prove, we can inductively assume
1682 // // this is inactive
1683
1684 // // As an optimization if we are going down already
1685 // // and we won't use ourselves (done by PHI's), we
1686 // // dont need to inductively assume we're true
1687 // // and can instead use this object!
1688 // // This pointer is inactive if it is either not actively stored to or
1689 // // not actively loaded from
1690 // // See alloca logic to explain why OnlyStores is insufficient here
1691 // if (directions == DOWN) {
1692 // if (isValueInactiveFromUsers(TR, Val, UseActivity::OnlyLoads)) {
1693 // InsertConstantValue(TR, Val);
1694 // return true;
1695 // }
1696 // } else {
1697 // Instruction *LoadReval = nullptr;
1698 // Instruction *StoreReval = nullptr;
1699 // auto DownHypothesis =
1700 // std::shared_ptr<mlir::enzyme::ActivityAnalyzer>(
1701 // new mlir::enzyme::ActivityAnalyzer(*this, DOWN));
1702 // DownHypothesis->ConstantValues.insert(Val);
1703 // if (DownHypothesis->isValueInactiveFromUsers(
1704 // TR, Val, UseActivity::OnlyLoads, &LoadReval) ||
1705 // (TR.query(GI)[{-1, -1}].isFloat() &&
1706 // DownHypothesis->isValueInactiveFromUsers(
1707 // TR, Val, UseActivity::OnlyStores, &StoreReval))) {
1708 // insertConstantsFrom(TR, *DownHypothesis);
1709 // InsertConstantValue(TR, Val);
1710 // return true;
1711 // } else {
1712 // if (LoadReval) {
1713 // if (EnzymePrintActivity)
1714 // llvm::errs() << " global activity of " << *Val
1715 // << " dependant on " << *LoadReval << "\n";
1716 // ReEvaluateValueIfInactiveInst[LoadReval].insert(Val);
1717 // }
1718 // if (StoreReval)
1719 // ReEvaluateValueIfInactiveInst[StoreReval].insert(Val);
1720 // }
1721 // }
1722 // }
1723 // }
1724
1725 // // Otherwise we have to assume this global is active since it can
1726 // // be arbitrarily used in an active way
1727 // // TODO we can be more aggressive here in the future
1728 // if (EnzymePrintActivity)
1729 // llvm::errs() << " VALUE nonconst unknown global " << *Val << " type - "
1730 // << res.str() << "\n";
1731 // ActiveValues.insert(Val);
1732 // return false;
1733 // }
1734
1735 //
1736 // TODO: constants are just operations in MLIR, this should be handled via
1737 // interfaces.
1738 //
1739 // ConstantExpr's are inactive if their arguments are inactive
1740 // Note that since there can't be a recursive constant this shouldn't
1741 // infinite loop
1742 // if (auto ce = dyn_cast<ConstantExpr>(Val)) {
1743 // if (ce->isCast()) {
1744 // if (isConstantValue(TR, ce->getOperand(0))) {
1745 // if (EnzymePrintActivity)
1746 // llvm::errs() << " VALUE const cast from from operand " << *Val
1747 // << "\n";
1748 // InsertConstantValue(TR, Val);
1749 // return true;
1750 // }
1751 // }
1752 // if (ce->getOpcode() == Instruction::GetElementPtr &&
1753 // llvm::all_of(ce->operand_values(),
1754 // [&](Value *v) { return isConstantValue(TR, v); })) {
1755 // if (isConstantValue(TR, ce->getOperand(0))) {
1756 // if (EnzymePrintActivity)
1757 // llvm::errs() << " VALUE const cast from gep operand " << *Val <<
1758 // "\n";
1759 // InsertConstantValue(TR, Val);
1760 // return true;
1761 // }
1762 // }
1763 // if (EnzymePrintActivity)
1764 // llvm::errs() << " VALUE nonconst unknown expr " << *Val << "\n";
1765 // ActiveValues.insert(Val);
1766 // return false;
1767 // }
1768
1769 if (auto CI = Val.getDefiningOp<CallOpInterface>()) {
1770
1771 if (CI->hasAttr("enzyme_active")) {
1773 llvm::errs() << "forced active val " << Val << "\n";
1774 ActiveValues.insert(Val);
1775 return false;
1776 }
1777 if (CI->hasAttr("enzyme_inactive")) {
1779 llvm::errs() << "forced inactive val " << Val << "\n";
1780 InsertConstantValue(TR, Val);
1781 return true;
1782 }
1783 Operation *called = getFunctionFromCall(CI);
1784 if (called) {
1785 if (called->hasAttr("enzyme_active")) {
1787 llvm::errs() << "forced active val " << Val << "\n";
1788 ActiveValues.insert(Val);
1789 return false;
1790 }
1791 if (called->hasAttr("enzyme_inactive")) {
1793 llvm::errs() << "forced inactive val " << Val << "\n";
1794 InsertConstantValue(TR, Val);
1795 return true;
1796 }
1797 }
1798 }
1799
1800 std::shared_ptr<mlir::enzyme::ActivityAnalyzer> UpHypothesis;
1801
1802 // Handle types that could contain pointers
1803 // Consider all types except
1804 // * floating point types (since those are assumed not pointers)
1805 // * integers that we know are not pointers
1806 //
1807 // TODO: this needs to go through type interfaces.
1808 //
1809 bool containsPointer = true;
1810 Type vectorTypeOrSelf =
1811 isa<mlir::VectorType>(Val.getType())
1812 ? cast<mlir::VectorType>(Val.getType()).getElementType()
1813 : Val.getType();
1814 if (LLVM::isCompatibleFloatingPointType(vectorTypeOrSelf))
1815 containsPointer = false;
1816 // if (!TR.intType(1, Val, /*errIfNotFound*/ false).isPossiblePointer())
1817
1818 auto typeIface = dyn_cast<AutoDiffTypeInterface>(Val.getType());
1819 if (!typeIface || !typeIface.isMutable()) {
1820 containsPointer = false;
1821 }
1822
1823 if (containsPointer && !isValuePotentiallyUsedAsPointer(Val)) {
1824 containsPointer = false;
1825 }
1826
1827 //
1828 // TODO: support pointers; this will likely need type analysis, a better
1829 // aliasing analysis than what MLIR currently offers, and some generalization
1830 // across types to understand what a pointer in MLIR type system.
1831 //
1832 if (containsPointer) {
1833
1834 Value TmpOrig = oputils::getBaseObject(Val);
1835
1836 // If we know that our origin is inactive from its arguments,
1837 // we are definitionally inactive
1838 if (directions & UP) {
1839 // If we are derived from an argument our activity is equal to the
1840 // activity of the argument by definition
1841 if (auto func = getFunctionIfArgument(TmpOrig)) {
1842 if (!func.getArgAttr(cast<BlockArgument>(TmpOrig).getArgNumber(),
1843 LLVM::LLVMDialect::getByValAttrName())) {
1844 bool res = isConstantValue(TR, TmpOrig);
1845 if (res) {
1847 llvm::errs() << " arg const from orig val=" << Val
1848 << " orig=" << TmpOrig << "\n";
1849 InsertConstantValue(TR, Val);
1850 } else {
1852 llvm::errs() << " arg active from orig val=" << Val
1853 << " orig=" << TmpOrig << "\n";
1854 ActiveValues.insert(Val);
1855 }
1856 return res;
1857 }
1858 }
1859
1860 if (auto op = TmpOrig.getDefiningOp())
1861 if (auto ifaceOp = dyn_cast<enzyme::ActivityOpInterface>(op)) {
1862 if (ifaceOp.isInactive()) {
1863 InsertConstantValue(TR, Val);
1864 if (TmpOrig != Val) {
1865 InsertConstantValue(TR, TmpOrig);
1866 }
1867 return true;
1868 }
1869 }
1870
1871 UpHypothesis = std::shared_ptr<mlir::enzyme::ActivityAnalyzer>(
1872 new mlir::enzyme::ActivityAnalyzer(*this, UP));
1873 UpHypothesis->ConstantValues.insert(Val);
1874
1875 // If our origin is a load of a known inactive (say inactive argument), we
1876 // are also inactive
1877 if (auto blockArg = dyn_cast<BlockArgument>(TmpOrig)) {
1878 // Not taking fast path in case blocks argument sources are recursive.
1879 Value active = nullptr;
1880 for (Value V : getPotentialIncomingValues(blockArg)) {
1881 if (!UpHypothesis->isConstantValue(TR, V)) {
1882 active = V;
1883 break;
1884 }
1885 }
1886 if (!active) {
1887 InsertConstantValue(TR, Val);
1888 if (TmpOrig != Val) {
1889 InsertConstantValue(TR, TmpOrig);
1890 }
1891 insertConstantsFrom(TR, *UpHypothesis);
1892 return true;
1893 } else {
1894 ReEvaluateValueIfInactiveValue[active].insert(Val);
1895 if (TmpOrig != Val) {
1896 ReEvaluateValueIfInactiveValue[active].insert(TmpOrig);
1897 }
1898 }
1899 } else if (auto LI = TmpOrig.getDefiningOp<LLVM::LoadOp>()) {
1900 if (directions == UP) {
1901 if (isConstantValue(TR, LI.getAddr())) {
1902 InsertConstantValue(TR, Val);
1903 return true;
1904 }
1905 } else {
1906 if (UpHypothesis->isConstantValue(TR, LI.getAddr())) {
1907 InsertConstantValue(TR, Val);
1908 insertConstantsFrom(TR, *UpHypothesis);
1909 return true;
1910 }
1911 }
1912 // } else if (isa<IntrinsicInst>(TmpOrig) &&
1913 // (cast<IntrinsicInst>(TmpOrig)->getIntrinsicID() ==
1914 // Intrinsic::nvvm_ldu_global_i ||
1915 // cast<IntrinsicInst>(TmpOrig)->getIntrinsicID() ==
1916 // Intrinsic::nvvm_ldu_global_p ||
1917 // cast<IntrinsicInst>(TmpOrig)->getIntrinsicID() ==
1918 // Intrinsic::nvvm_ldu_global_f ||
1919 // cast<IntrinsicInst>(TmpOrig)->getIntrinsicID() ==
1920 // Intrinsic::nvvm_ldg_global_i ||
1921 // cast<IntrinsicInst>(TmpOrig)->getIntrinsicID() ==
1922 // Intrinsic::nvvm_ldg_global_p ||
1923 // cast<IntrinsicInst>(TmpOrig)->getIntrinsicID() ==
1924 // Intrinsic::nvvm_ldg_global_f)) {
1925 // auto II = cast<IntrinsicInst>(TmpOrig);
1926 // if (directions == UP) {
1927 // if (isConstantValue(TR, II->getOperand(0))) {
1928 // InsertConstantValue(TR, Val);
1929 // return true;
1930 // }
1931 // } else {
1932 // if (UpHypothesis->isConstantValue(TR, II->getOperand(0))) {
1933 // InsertConstantValue(TR, Val);
1934 // insertConstantsFrom(TR, *UpHypothesis);
1935 // return true;
1936 // }
1937 // }
1938 } else if (auto op = TmpOrig.getDefiningOp<CallOpInterface>()) {
1939 if (op->hasAttr("enzyme_inactive")) {
1940 InsertConstantValue(TR, Val);
1941 insertConstantsFrom(TR, *UpHypothesis);
1942 return true;
1943 }
1944 Operation *called = getFunctionFromCall(op);
1945
1946 StringRef funcName =
1947 called ? cast<SymbolOpInterface>(called).getName() : "";
1948
1949 if (called && called->hasAttr("enzyme_inactive")) {
1950 InsertConstantValue(TR, Val);
1951 insertConstantsFrom(TR, *UpHypothesis);
1952 return true;
1953 }
1954 if (funcName == "free" || funcName == "_ZdlPv" ||
1955 funcName == "_ZdlPvm" || funcName == "munmap") {
1956 InsertConstantValue(TR, Val);
1957 insertConstantsFrom(TR, *UpHypothesis);
1958 return true;
1959 }
1960
1961 auto dName = llvm::demangle(funcName.str());
1962 for (auto FuncName : DemangledKnownInactiveFunctionsStartingWith) {
1963 if (startsWith(dName, FuncName)) {
1964 InsertConstantValue(TR, Val);
1965 insertConstantsFrom(TR, *UpHypothesis);
1966 return true;
1967 }
1968 }
1969
1970 for (auto FuncName : KnownInactiveFunctionsStartingWith) {
1971 if (startsWith(funcName, FuncName)) {
1972 InsertConstantValue(TR, Val);
1973 insertConstantsFrom(TR, *UpHypothesis);
1974 return true;
1975 }
1976 }
1977
1978 for (auto FuncName : KnownInactiveFunctionsContains) {
1979 if (funcName.contains(FuncName)) {
1980 InsertConstantValue(TR, Val);
1981 insertConstantsFrom(TR, *UpHypothesis);
1982 return true;
1983 }
1984 }
1985
1986 if (KnownInactiveFunctions.count(funcName.str()) ||
1987 MPIInactiveCommAllocators.find(funcName.str()) !=
1989 InsertConstantValue(TR, Val);
1990 insertConstantsFrom(TR, *UpHypothesis);
1991 return true;
1992 }
1993
1994 // if (called && called->getIntrinsicID() == Intrinsic::trap) {
1995 // InsertConstantValue(TR, Val);
1996 // insertConstantsFrom(TR, *UpHypothesis);
1997 // return true;
1998 // }
1999
2000 // If requesting empty unknown functions to be considered inactive,
2001 // abide by those rules
2002 //
2003 // TODO: support this as pass/configuration option in MLIR
2004 //
2005 // if (called && EnzymeEmptyFnInactive && called->empty() &&
2006 // !hasMetadata(called, "enzyme_gradient") &&
2007 // !hasMetadata(called, "enzyme_derivative") &&
2008 // !isAllocationFunction(funcName, TLI) &&
2009 // !isDeallocationFunction(funcName, TLI) &&
2010 // !isa<IntrinsicInst>(op)) {
2011 // InsertConstantValue(TR, Val);
2012 // insertConstantsFrom(TR, *UpHypothesis);
2013 // return true;
2014 // }
2015 // if (isAllocationFunction(funcName, TLI)) {
2016 // // This pointer is inactive if it is either not actively stored to
2017 // // and not actively loaded from.
2018 // if (directions == DOWN) {
2019 // for (auto UA :
2020 // {UseActivity::OnlyLoads, UseActivity::OnlyNonPointerStores,
2021 // UseActivity::AllStores, UseActivity::None}) {
2022 // Instruction *LoadReval = nullptr;
2023 // if (isValueInactiveFromUsers(TR, TmpOrig, UA, &LoadReval)) {
2024 // InsertConstantValue(TR, Val);
2025 // return true;
2026 // }
2027 // if (LoadReval && UA != UseActivity::AllStores) {
2028 // ReEvaluateValueIfInactiveInst[LoadReval].insert(TmpOrig);
2029 // }
2030 // }
2031 // } else if (directions & DOWN) {
2032 // auto DownHypothesis =
2033 // std::shared_ptr<mlir::enzyme::ActivityAnalyzer>(
2034 // new mlir::enzyme::ActivityAnalyzer(*this, DOWN));
2035 // DownHypothesis->ConstantValues.insert(TmpOrig);
2036 // for (auto UA :
2037 // {UseActivity::OnlyLoads, UseActivity::OnlyNonPointerStores,
2038 // UseActivity::AllStores, UseActivity::None}) {
2039 // Instruction *LoadReval = nullptr;
2040 // if (DownHypothesis->isValueInactiveFromUsers(TR, TmpOrig, UA,
2041 // &LoadReval)) {
2042 // insertConstantsFrom(TR, *DownHypothesis);
2043 // InsertConstantValue(TR, Val);
2044 // return true;
2045 // } else {
2046 // if (LoadReval && UA != UseActivity::AllStores) {
2047 // ReEvaluateValueIfInactiveInst[LoadReval].insert(TmpOrig);
2048 // }
2049 // }
2050 // }
2051 // }
2052 // }
2053 if (funcName == "jl_array_copy" || funcName == "ijl_array_copy" ||
2054 funcName == "jl_idtable_rehash" ||
2055 funcName == "ijl_idtable_rehash" ||
2056 funcName == "jl_genericmemory_copy_slice" ||
2057 funcName == "ijl_genericmemory_copy_slice") {
2058 // This pointer is inactive if it is either not actively stored to
2059 // and not actively loaded from.
2060 if (directions & DOWN && directions & UP) {
2061 if (UpHypothesis->isConstantValue(TR, op->getOperand(0))) {
2062 auto DownHypothesis =
2063 std::shared_ptr<mlir::enzyme::ActivityAnalyzer>(
2064 new mlir::enzyme::ActivityAnalyzer(*this, DOWN));
2065 DownHypothesis->ConstantValues.insert(TmpOrig);
2066 for (auto UA :
2067 {UseActivity::OnlyLoads, UseActivity::OnlyNonPointerStores,
2068 UseActivity::AllStores, UseActivity::None}) {
2069 Operation *LoadReval = nullptr;
2070 if (DownHypothesis->isValueInactiveFromUsers(TR, TmpOrig, UA,
2071 &LoadReval)) {
2072 insertConstantsFrom(TR, *DownHypothesis);
2073 InsertConstantValue(TR, Val);
2074 return true;
2075 } else {
2076 if (LoadReval && UA != UseActivity::AllStores) {
2077 ReEvaluateValueIfInactiveOp[LoadReval].insert(TmpOrig);
2078 }
2079 }
2080 }
2081 }
2082 }
2083 }
2084 } else if (Val.getDefiningOp<LLVM::AllocaOp>()) {
2085 // This pointer is inactive if it is either not actively stored to or
2086 // not actively loaded from and is nonescaping by definition of being
2087 // alloca OnlyStores is insufficient here since the loaded pointer can
2088 // have active memory stored into it [e.g. not just top level pointer
2089 // that matters]
2090 if (directions == DOWN) {
2091 for (auto UA :
2092 {UseActivity::OnlyLoads, UseActivity::OnlyNonPointerStores,
2093 UseActivity::AllStores, UseActivity::None}) {
2094 Operation *LoadReval = nullptr;
2095 if (isValueInactiveFromUsers(TR, TmpOrig, UA, &LoadReval)) {
2096 InsertConstantValue(TR, Val);
2097 return true;
2098 }
2099 if (LoadReval && UA != UseActivity::AllStores) {
2100 ReEvaluateValueIfInactiveOp[LoadReval].insert(TmpOrig);
2101 }
2102 }
2103 } else if (directions & DOWN) {
2104 auto DownHypothesis = std::shared_ptr<mlir::enzyme::ActivityAnalyzer>(
2105 new mlir::enzyme::ActivityAnalyzer(*this, DOWN));
2106 DownHypothesis->ConstantValues.insert(TmpOrig);
2107 for (auto UA :
2108 {UseActivity::OnlyLoads, UseActivity::OnlyNonPointerStores,
2109 UseActivity::AllStores, UseActivity::None}) {
2110 Operation *LoadReval = nullptr;
2111 if (DownHypothesis->isValueInactiveFromUsers(TR, TmpOrig, UA,
2112 &LoadReval)) {
2113 insertConstantsFrom(TR, *DownHypothesis);
2114 InsertConstantValue(TR, Val);
2115 return true;
2116 } else {
2117 if (LoadReval && UA != UseActivity::AllStores) {
2118 ReEvaluateValueIfInactiveOp[LoadReval].insert(TmpOrig);
2119 }
2120 }
2121 }
2122 }
2123 }
2124
2125 // otherwise if the origin is a previously derived known inactive value
2126 // assess
2127 // TODO here we would need to potentially consider loading an active
2128 // global as we again assume that active memory is passed explicitly as an
2129 // argument
2130 if (TmpOrig != Val) {
2131 if (isConstantValue(TR, TmpOrig)) {
2133 llvm::errs() << " Potential Pointer(" << (int)directions << ") "
2134 << Val << " inactive from inactive origin " << TmpOrig
2135 << "\n";
2136 InsertConstantValue(TR, Val);
2137 return true;
2138 }
2139 }
2140 if (!getFunctionIfArgument(Val)) {
2141 Operation *op = Val.getDefiningOp();
2142 // No defining op means the value is a block argument and blocks don't
2143 // have their own memory semantics.
2144 if (!op || (!mayReadFromMemory(op) && !mayAllocateMemory(op))) {
2145 if (directions == UP && !isa<BlockArgument>(Val)) {
2146 if (isValueInactiveFromOrigin(TR, Val)) {
2148 llvm::errs() << " Non-function value inactive from origin("
2149 << (int)directions << ") " << Val << "\n";
2150 InsertConstantValue(TR, Val);
2151 return true;
2152 }
2153 } else {
2154 if (UpHypothesis->isValueInactiveFromOrigin(TR, Val)) {
2156 llvm::errs() << " Non-function value_v2 inactive from origin("
2157 << (int)directions << ") " << Val << "\n";
2158 InsertConstantValue(TR, Val);
2159 insertConstantsFrom(TR, *UpHypothesis);
2160 return true;
2161 }
2162 }
2163 }
2164 }
2165 }
2166
2167 // If not capable of looking at both users and uses, all the ways a pointer
2168 // can be loaded/stored cannot be assesed and therefore we default to assume
2169 // it to be active
2170 if (directions != (UP | DOWN)) {
2172 llvm::errs() << " <Potential Pointer assumed active at "
2173 << (int)directions << ">" << Val << "\n";
2174 ActiveValues.insert(Val);
2175 return false;
2176 }
2177
2178 // if (EnzymePrintActivity)
2179 // llvm::errs() << " < MEMSEARCH" << (int)directions << ">" << *Val <<
2180 // "\n";
2181 // A pointer value is active if two things hold:
2182 // an potentially active value is stored into the memory
2183 // memory loaded from the value is used in an active way
2184 bool potentiallyActiveStore = false;
2185 bool potentialStore = false;
2186 bool potentiallyActiveLoad = false;
2187
2188 // Assume the value (not instruction) is itself active
2189 // In spite of that can we show that there are either no active stores
2190 // or no active loads
2191 std::shared_ptr<mlir::enzyme::ActivityAnalyzer> Hypothesis =
2192 std::shared_ptr<mlir::enzyme::ActivityAnalyzer>(
2193 new mlir::enzyme::ActivityAnalyzer(*this, directions));
2194 Hypothesis->ActiveValues.insert(Val);
2195 if (!getFunctionIfArgument(Val)) {
2196 for (Value V : DeducingPointers) {
2197 UpHypothesis->InsertConstantValue(TR, V);
2198 }
2199 if (UpHypothesis->isValueInactiveFromOrigin(TR, Val)) {
2200 Hypothesis->DeducingPointers.insert(Val);
2202 llvm::errs() << " constant instruction hypothesis: " << Val << "\n";
2203 } else {
2205 llvm::errs() << " cannot show constant instruction hypothesis: "
2206 << Val << "\n";
2207 }
2208 }
2209
2210 auto checkActivity = [&](Operation *op) {
2211 if (notForAnalysis.count(op->getBlock()))
2212 return false;
2213
2214 if (auto op = TmpOrig.getDefiningOp())
2215 if (auto ifaceOp = dyn_cast<enzyme::ActivityOpInterface>(op)) {
2216 if (ifaceOp.isInactive()) {
2217 return false;
2218 }
2219 }
2220
2221 // If this is a malloc or free, this doesn't impact the activity
2222 if (auto CI = dyn_cast<CallOpInterface>(op)) {
2223 if (CI->hasAttr("enzyme_inactive"))
2224 return false;
2225
2226 Operation *F = getFunctionFromCall(CI);
2227 StringRef funcName = F ? cast<SymbolOpInterface>(F).getName() : "";
2228
2229 if (F && F->hasAttr("enzyme_inactive")) {
2230 return false;
2231 }
2232 // if (isAllocationFunction(funcName, TLI) ||
2233 // isDeallocationFunction(funcName, TLI)) {
2234 // return false;
2235 // }
2236 if (KnownInactiveFunctions.count(funcName.str()) ||
2237 MPIInactiveCommAllocators.find(funcName.str()) !=
2239 return false;
2240 }
2241 if (KnownInactiveFunctionInsts.count(funcName.str())) {
2242 return false;
2243 }
2244 // if (isMemFreeLibMFunction(funcName) || funcName == "__fd_sincos_1") {
2245 // return false;
2246 // }
2247
2248 auto dName = llvm::demangle(funcName.str());
2249 for (auto FuncName : DemangledKnownInactiveFunctionsStartingWith) {
2250 if (startsWith(dName, FuncName)) {
2251 return false;
2252 }
2253 }
2254
2255 for (auto FuncName : KnownInactiveFunctionsStartingWith) {
2256 if (startsWith(funcName, FuncName)) {
2257 return false;
2258 }
2259 }
2260 for (auto FuncName : KnownInactiveFunctionsContains) {
2261 if (funcName.contains(FuncName)) {
2262 return false;
2263 }
2264 }
2265
2266 if (funcName == "__cxa_guard_acquire" ||
2267 funcName == "__cxa_guard_release" ||
2268 funcName == "__cxa_guard_abort" || funcName == "posix_memalign") {
2269 return false;
2270 }
2271 }
2272
2273 // When the value is not a pointer, find a pointer value it is casted from
2274 // or to. This was necessary for LLVM BasicAA, but is currently irrelevant
2275 // for MLIR as it doesn't have robust AA anyway.
2276 // TODO: this probably needs a MLIR interface, or nothing at all depending
2277 // on how AA will work.
2278 // Value memval = Val;
2279 // if (!memval.getType().isa<LLVM::LLVMPointerType, MemRefType>()) {
2280 // if (auto cast = Val.getDefiningOp<CastOpInterface>()) {
2281 // if (cast->getNumOperands() == 1 && cast->getNumResults() == 1) {
2282 // if (cast->getOperand(0).getType().isa<LLVM::LLVMPointerType,
2283 // MemRefType>())
2284 // memval = cast->getOperand(0);
2285 // }
2286 // }
2287 // for (Operation *user : Val.getUsers()) {
2288 // if (isa<CastOpInterface>(user) && user->getNumOperands() == 1 &&
2289 // user->getNumResults() == 1 &&
2290 // user->getResult(0)
2291 // .getType()
2292 // .isa<LLVM::LLVMPointerType, MemRefType>()) {
2293 // memval = user->getResult(0);
2294 // break;
2295 // }
2296 // }
2297 // }
2298
2299 // #if LLVM_VERSION_MAJOR >= 12
2300 // auto AARes = AA.getModRefInfo(
2301 // I, MemoryLocation(memval,
2302 // LocationSize::beforeOrAfterPointer()));
2303 // #else
2304 // auto AARes =
2305 // AA.getModRefInfo(I, MemoryLocation(memval,
2306 // LocationSize::unknown()));
2307 // #endif
2308
2309 // TODO: MLIR doesn't have robust alias analysis, fall back to assuming we
2310 // may modify or reference any location when the operation has the
2311 // corresponding memory effects. When there proper AA exists, this may not
2312 // be necessary at all or only useful as a fallback when the value is not
2313 // pointer-like and we cannot discover a corresponding pointer by looking
2314 // through cast operations.
2315 bool mayRead = mayReadFromMemory(op);
2316 bool mayWrite = mayWriteToMemory(op);
2317 llvm::ModRefInfo modRef =
2318 mayRead
2319 ? (mayWrite ? llvm::ModRefInfo::ModRef : llvm::ModRefInfo::Ref)
2320 : (mayWrite ? llvm::ModRefInfo::Mod : llvm::ModRefInfo::NoModRef);
2321
2322 // TODO: there is no such attribute in MLIR.
2323 // if (auto CB = dyn_cast<CallInst>(I)) {
2324 // if (CB->onlyAccessesInaccessibleMemory())
2325 // AARes = ModRefInfo::NoModRef;
2326 // }
2327
2328 // TODO this aliasing information is too conservative, the question
2329 // isn't merely aliasing but whether there is a path for THIS value to
2330 // eventually be loaded by it not simply because there isnt aliasing
2331
2332 // If we haven't already shown a potentially active load
2333 // check if this loads the given value and is active
2334 if (!potentiallyActiveLoad && isRefSet(modRef)) {
2336 llvm::errs() << "potential active load: " << *op << "\n";
2337 if (isa<memref::LoadOp, LLVM::LoadOp>(op)) {
2338 // TODO: this assumption should be built into the MLIR interface
2339 // verifier, or alternatively we should relax it.
2340 assert(op->getNumResults() == 1 &&
2341 "expected a load-like op to have one result");
2342 // If the ref'ing value is a load check if the loaded value is
2343 // active
2344 Value loadedValue = op->getResult(0);
2345 if (!Hypothesis->isConstantValue(TR, loadedValue)) {
2346 potentiallyActiveLoad = true;
2347 // returns whether seen
2348 std::function<bool(Value V, SmallPtrSetImpl<Value> &)> loadCheck =
2349 [&](Value V, SmallPtrSetImpl<Value> &Seen) {
2350 if (Seen.count(V))
2351 return false;
2352 Seen.insert(V);
2353 // TODO: in absence of MLIR type analysis, assume everything
2354 // is a possible pointer.
2355 //
2356 if (TR.query(V)[{-1}].isPossiblePointer()) {
2357 for (Operation *user : V.getUsers()) {
2358 if (mayWriteToMemory(user)) {
2359 if (!Hypothesis->isConstantOperation(TR, user)) {
2361 llvm::errs() << "potential active store via "
2362 "pointer in load: "
2363 << *op << " of " << Val << " via "
2364 << *user << "\n";
2365 potentiallyActiveStore = true;
2366 return true;
2367 }
2368 }
2369
2370 // TODO: XXX HELP WANTED XXX
2371 //
2372 for (Value U : user->getResults()) {
2373 if (U != Val && !Hypothesis->isConstantValue(TR, U)) {
2374 if (loadCheck(U, Seen))
2375 return true;
2376 }
2377 }
2378 }
2379 }
2380 return false;
2381 };
2382 SmallPtrSet<Value, 2> Seen;
2383 loadCheck(loadedValue, Seen);
2384 }
2385 } else if (isa<LLVM::MemcpyOp, LLVM::MemmoveOp, LLVM::MemcpyInlineOp>(
2386 op)) {
2387 if (!Hypothesis->isConstantValue(TR, op->getOperand(0))) {
2388 potentiallyActiveLoad = true;
2389 // TODO: in absence of MLIR type analysis, assume everything is a
2390 // possible pointer.
2391 //
2392 // if (TR.query(Val)[{-1, -1}].isPossiblePointer()) {
2393 // if (EnzymePrintActivity)
2394 // llvm::errs()
2395 // << "potential active store via pointer in memcpy: " << *I
2396 // << " of " << *Val << "\n";
2397 potentiallyActiveStore = true;
2398 // }
2399 }
2400 } else {
2401 // Otherwise fallback and check any part of the instruction is
2402 // active
2403 // TODO: note that this can be optimized (especially for function
2404 // calls)
2405 // Notably need both to check the result and instruction since
2406 // A load that has as result an active pointer is not an active
2407 // instruction, but does have an active value
2408
2409 //
2410 // TODO: XXX HELP WANTED XXX
2411 //
2412 if (!Hypothesis->isConstantOperation(TR, op) ||
2413 llvm::any_of(op->getResults(), [&](Value V) {
2414 if (V == Val)
2415 return false;
2416 return !Hypothesis->isConstantValue(TR, V);
2417 })) {
2418 potentiallyActiveLoad = true;
2419 // If this a potential pointer of pointer AND
2420 // double** Val;
2421 //
2422 // TODO: in absence of MLIR type analysis, assume everything is a
2423 // possible pointer.
2424 if (TR.query(Val)[{-1, -1}].isPossiblePointer()) {
2425 // If this instruction either:
2426 // 1) can actively store into the inner pointer, even
2427 // if it doesn't store into the outer pointer. Actively
2428 // storing into the outer pointer is handled by the isMod
2429 // case.
2430 // I(double** readonly Val, double activeX) {
2431 // double* V0 = Val[0]
2432 // V0 = activeX;
2433 // }
2434 // 2) may return an active pointer loaded from Val
2435 // double* I = *Val;
2436 // I[0] = active;
2437 //
2438 if ((mayWriteToMemory(op) &&
2439 !Hypothesis->isConstantOperation(TR, op)) ||
2440 llvm::any_of(op->getResults(), [&](Value V) -> bool {
2441 return !Hypothesis->DeducingPointers.count(V) &&
2442 !Hypothesis->isConstantValue(TR, V) &&
2443 TR.query(V)[{-1}].isPossiblePointer();
2444 })) {
2446 llvm::errs() << "potential active store via pointer in "
2447 "unknown inst: "
2448 << *op << " of " << Val << "\n";
2449 potentiallyActiveStore = true;
2450 }
2451 }
2452 }
2453 }
2454 }
2455 if ((!potentiallyActiveStore || !potentialStore) && isModSet(modRef)) {
2457 llvm::errs() << "potential active store: " << *op << " Val=" << Val
2458 << "\n";
2459 if (auto SI = dyn_cast<enzyme::StoreLikeInterface>(op)) {
2460 bool cop = !Hypothesis->isConstantValue(TR, SI.getStoredValue());
2462 llvm::errs() << " -- store potential activity: " << (int)cop
2463 << " - " << *op << " of "
2464 << " Val=" << Val << "\n";
2465 potentialStore = true;
2466 if (cop)
2467 potentiallyActiveStore = true;
2468 } else if (isa<LLVM::MemcpyOp, LLVM::MemmoveOp, LLVM::MemcpyInlineOp>(
2469 op)) {
2470 bool cop = !Hypothesis->isConstantValue(TR, op->getOperand(1));
2471 potentialStore = true;
2472 if (cop)
2473 potentiallyActiveStore = true;
2474 } else if (isa<LLVM::MemsetOp>(op)) {
2475 potentialStore = true;
2476 } else {
2477 // Otherwise fallback and check if the instruction is active
2478 // TODO: note that this can be optimized (especially for function
2479 // calls)
2480 auto cop = !Hypothesis->isConstantOperation(TR, op);
2482 llvm::errs() << " -- unknown store potential activity: " << (int)cop
2483 << " - " << *op << " of "
2484 << " Val=" << Val << "\n";
2485 potentialStore = true;
2486 if (cop)
2487 potentiallyActiveStore = true;
2488 }
2489 }
2490 if (potentiallyActiveStore && potentiallyActiveLoad)
2491 return true;
2492 return false;
2493 };
2494
2495 // Search through all the instructions in this function
2496 // for potential loads / stores of this value.
2497 //
2498 // We can choose to only look at potential follower instructions
2499 // if the value is created by the instruction (alloca, noalias)
2500 // since no potentially active store to the same location can occur
2501 // prior to its creation. Otherwise, check all instructions in the
2502 // function as a store to an aliasing location may have occurred
2503 // prior to the instruction generating the value.
2504
2505 // Check activity modifies the captured variables, and also returns a value
2506 // that indicates whether activity was found and we should stop.
2507 auto checkActivityWrapper = [checkActivity](Operation *op) {
2508 if (checkActivity(op))
2509 return WalkResult::interrupt();
2510 return WalkResult::advance();
2511 };
2512
2513 if (auto VI = Val.getDefiningOp<LLVM::AllocaOp>()) {
2514 allFollowersOf(VI, checkActivityWrapper);
2515 } else if (auto VI = Val.getDefiningOp<memref::AllocaOp>()) {
2516 allFollowersOf(VI, checkActivityWrapper);
2517 // } else if (auto VI = dyn_cast<CallInst>(Val)) {
2518 // if (VI->hasRetAttr(Attribute::NoAlias))
2519 // allFollowersOf(VI, checkActivity);
2520 // else {
2521 // for (BasicBlock &BB : *TR.getFunction()) {
2522 // if (notForAnalysis.count(&BB))
2523 // continue;
2524 // for (Instruction &I : BB) {
2525 // if (checkActivity(&I))
2526 // goto activeLoadAndStore;
2527 // }
2528 // }
2529 // }
2530 //
2531 } else {
2532 Operation *func = Val.getParentBlock()->getParentOp();
2533 assert(func && "unexpected freestanding block");
2534 if (!isa<FunctionOpInterface>(func))
2535 func = func->getParentOfType<FunctionOpInterface>();
2536
2537 WalkResult r = func->walk([checkActivity, this](Operation *nested) {
2538 if (notForAnalysis.contains(nested->getBlock()))
2539 return WalkResult::skip();
2540 if (checkActivity(nested))
2541 return WalkResult::interrupt();
2542 return WalkResult::advance();
2543 });
2544 if (r.wasInterrupted())
2545 goto activeLoadAndStore;
2546 }
2547
2548 activeLoadAndStore:;
2550 llvm::errs() << " </MEMSEARCH" << (int)directions << ">" << Val
2551 << " potentiallyActiveLoad=" << potentiallyActiveLoad
2552 << " potentiallyActiveStore=" << potentiallyActiveStore
2553 << " potentialStore=" << potentialStore << "\n";
2554 if (potentiallyActiveLoad && potentiallyActiveStore) {
2555 insertAllFrom(TR, *Hypothesis, Val);
2556 // TODO have insertall dependence on this
2557 if (TmpOrig != Val)
2558 ReEvaluateValueIfInactiveValue[TmpOrig].insert(Val);
2559 return false;
2560 } else {
2561 // We now know that there isn't a matching active load/store pair in this
2562 // function. Now the only way that this memory can facilitate a transfer
2563 // of active information is if it is done outside of the function
2564
2565 // This can happen if either:
2566 // a) the memory had an active load or store before this function was
2567 // called b) the memory had an active load or store after this function
2568 // was called
2569
2570 // Case a) can occur if:
2571 // 1) this memory came from an active global
2572 // 2) this memory came from an active argument
2573 // 3) this memory came from a load from active memory
2574 // In other words, assuming this value is inactive, going up this
2575 // location's argument must be inactive
2576
2577 assert(UpHypothesis);
2578 // UpHypothesis.ConstantValues.insert(val);
2579 if (DeducingPointers.size() == 0)
2580 UpHypothesis->insertConstantsFrom(TR, *Hypothesis);
2581 // for (auto V : DeducingPointers) {
2582 // UpHypothesis->InsertConstantValue(TR, V);
2583 // }
2584 assert(directions & UP);
2585 bool ActiveUp = !getFunctionIfArgument(Val) &&
2586 !UpHypothesis->isValueInactiveFromOrigin(TR, Val);
2587
2588 // Case b) can occur if:
2589 // 1) this memory is used as part of an active return
2590 // 2) this memory is stored somewhere
2591
2592 // We never verify that an origin wasn't stored somewhere or returned.
2593 // to remedy correctness for now let's do something extremely simple
2594 std::shared_ptr<mlir::enzyme::ActivityAnalyzer> DownHypothesis =
2595 std::shared_ptr<mlir::enzyme::ActivityAnalyzer>(
2596 new mlir::enzyme::ActivityAnalyzer(*this, DOWN));
2597 DownHypothesis->ConstantValues.insert(Val);
2598 DownHypothesis->insertConstantsFrom(TR, *Hypothesis);
2599 bool ActiveDown =
2600 DownHypothesis->isValueActivelyStoredOrReturned(TR, Val);
2601 // BEGIN TEMPORARY
2602
2603 if (!ActiveDown && TmpOrig != Val) {
2604
2606 TmpOrig) || /*isa<GlobalVariable>(TmpOrig) ||*/
2607 TmpOrig.getDefiningOp<LLVM::AllocaOp>() ||
2608 TmpOrig.getDefiningOp<memref::AllocaOp>() /*||
2609 isAllocationCall(TmpOrig, TLI)*/) {
2610 std::shared_ptr<mlir::enzyme::ActivityAnalyzer> DownHypothesis2 =
2611 std::shared_ptr<mlir::enzyme::ActivityAnalyzer>(
2612 new mlir::enzyme::ActivityAnalyzer(*DownHypothesis, DOWN));
2613 DownHypothesis2->ConstantValues.insert(TmpOrig);
2614 if (DownHypothesis2->isValueActivelyStoredOrReturned(TR, TmpOrig)) {
2616 llvm::errs() << " active from ivasor: " << TmpOrig << "\n";
2617 ActiveDown = true;
2618 }
2619 } else {
2620 // unknown origin that could've been stored/returned/etc
2622 llvm::errs() << " active from unknown origin: " << TmpOrig << "\n";
2623 ActiveDown = true;
2624 }
2625 }
2626
2627 // END TEMPORARY
2628
2629 // We can now consider the three places derivative information can be
2630 // transferred
2631 // Case A) From the origin
2632 // Case B) Though the return
2633 // Case C) Within the function (via either load or store)
2634
2635 bool ActiveMemory = false;
2636
2637 // If it is transferred via active origin and return, clearly this is
2638 // active
2639 ActiveMemory |= (ActiveUp && ActiveDown);
2640
2641 // If we come from an active origin and load, memory is clearly active
2642 ActiveMemory |= (ActiveUp && potentiallyActiveLoad);
2643
2644 // If we come from an active origin and only store into it, it changes
2645 // future state
2646 ActiveMemory |= (ActiveUp && potentialStore);
2647
2648 // If we go to an active return and store active memory, this is active
2649 ActiveMemory |= (ActiveDown && potentialStore);
2650 // Actually more generally, if we are ActiveDown (returning memory that is
2651 // used) in active return, we must be active. This is necessary to ensure
2652 // mallocs have their differential shadows created when returned [TODO
2653 // investigate more]
2654 ActiveMemory |= ActiveDown;
2655
2656 // If we go to an active return and only load it, however, that doesnt
2657 // transfer derivatives and we can say this memory is inactive
2658
2660 llvm::errs() << " @@MEMSEARCH" << (int)directions << ">" << Val
2661 << " potentiallyActiveLoad=" << potentiallyActiveLoad
2662 << " potentialStore=" << potentialStore
2663 << " ActiveUp=" << ActiveUp << " ActiveDown=" << ActiveDown
2664 << " ActiveMemory=" << ActiveMemory << "\n";
2665
2666 if (ActiveMemory) {
2667 ActiveValues.insert(Val);
2668 assert(Hypothesis->directions == directions);
2669 assert(Hypothesis->ActiveValues.count(Val));
2670 insertAllFrom(TR, *Hypothesis, Val);
2671 if (TmpOrig != Val)
2672 ReEvaluateValueIfInactiveValue[TmpOrig].insert(Val);
2673 return false;
2674 } else {
2675 InsertConstantValue(TR, Val);
2676 insertConstantsFrom(TR, *Hypothesis);
2677 if (DeducingPointers.size() == 0)
2678 insertConstantsFrom(TR, *UpHypothesis);
2679 insertConstantsFrom(TR, *DownHypothesis);
2680 return true;
2681 }
2682 }
2683 }
2684 // End TODO support pointers
2685
2686 // For all non-pointers, it is now sufficient to simply prove that
2687 // either activity does not flow in, or activity does not flow out
2688 // This alone cuts off the flow (being unable to flow through memory)
2689
2690 // Not looking at uses to prove inactive (definition of up), if the creator of
2691 // this value is inactive, we are inactive Since we won't look at uses to
2692 // prove, we can inductively assume this is inactive
2693 if (directions & UP) {
2694 UpHypothesis = std::shared_ptr<mlir::enzyme::ActivityAnalyzer>(
2695 new mlir::enzyme::ActivityAnalyzer(*this, UP));
2696 UpHypothesis->ConstantValues.insert(Val);
2697 SmallPtrSet<Value, 4> toredo;
2698 if (UpHypothesis->isValueInactiveFromOrigin(TR, Val, &toredo)) {
2699 insertConstantsFrom(TR, *UpHypothesis);
2700 InsertConstantValue(TR, Val);
2702 llvm::errs() << " Value constant from origin [" << (int)directions
2703 << "]" << Val << "\n";
2704 return true;
2705 } else {
2706 for (Value result : toredo) {
2707 if (result != Val)
2708 ReEvaluateValueIfInactiveValue[result].insert(Val);
2709 }
2710 }
2711 }
2712
2713 if (directions & DOWN) {
2714 // Not looking at users to prove inactive (definition of down)
2715 // If all users are inactive, this is therefore inactive.
2716 // Since we won't look at origins to prove, we can inductively assume this
2717 // is inactive
2718 auto DownHypothesis = std::shared_ptr<mlir::enzyme::ActivityAnalyzer>(
2719 new mlir::enzyme::ActivityAnalyzer(*this, DOWN));
2720 DownHypothesis->ConstantValues.insert(Val);
2721 if (DownHypothesis->isValueInactiveFromUsers(TR, Val, UseActivity::None)) {
2722 insertConstantsFrom(TR, *DownHypothesis);
2723 if (UpHypothesis)
2724 insertConstantsFrom(TR, *UpHypothesis);
2725 InsertConstantValue(TR, Val);
2726 return true;
2727 }
2728 }
2729
2731 llvm::errs() << " Value nonconstant (couldn't disprove)[" << (int)directions
2732 << "]" << Val << "\n";
2733 ActiveValues.insert(Val);
2734 return false;
2735}
2736
2737/// Is the value guaranteed to be inactive because of how it's produced.
2738bool mlir::enzyme::ActivityAnalyzer::isValueInactiveFromOrigin(
2739 MTypeResults const &TR, Value val, SmallPtrSetImpl<Value> *inactArg) {
2740 // Must be an analyzer only searching up
2741 assert(directions == UP);
2742
2743 if (auto arg = dyn_cast<BlockArgument>(val)) {
2744 for (auto v : getPotentialIncomingValues(arg)) {
2745 if (!isConstantValue(TR, v)) {
2746 if (EnzymePrintActivity) {
2747 llvm::errs() << " blockarg: " << arg
2748 << " may be active due to inflow from " << v << "\n";
2749 }
2750 if (inactArg)
2751 inactArg->insert(v);
2752 return false;
2753 }
2754 }
2755 return true;
2756 }
2757
2758 return isOperationInactiveFromOrigin(
2759 TR, val.getDefiningOp(), cast<OpResult>(val).getResultNumber(), inactArg);
2760}
2761
2762bool mlir::enzyme::ActivityAnalyzer::isOperationInactiveFromOrigin(
2763 MTypeResults const &TR, Operation *op, std::optional<unsigned> resultNo,
2764 SmallPtrSetImpl<Value> *inactArg) {
2765 // Must be an analyzer only searching up
2766 assert(directions == UP);
2767
2768 // Not an instruction and thus not legal to search for activity via operands
2769 if (!op) {
2770 llvm::errs() << "unknown pointer source";
2771 assert(0 && "unknown pointer source");
2772 llvm_unreachable("unknown pointer source");
2773 return false;
2774 }
2775
2776 if (auto ifaceOp = dyn_cast<enzyme::ActivityOpInterface>(op)) {
2777 if (ifaceOp.isInactive()) {
2778 return true;
2779 }
2780 }
2781
2783 llvm::errs() << " < UPSEARCH" << (int)directions << ">" << *op << "\n";
2784
2785 // if either src or dst is inactive, there cannot be a transfer of active
2786 // values and thus the store is inactive
2787 if (auto store = dyn_cast<enzyme::StoreLikeInterface>(op)) {
2788 if (isConstantValue(TR, store.getStoredValue()) ||
2789 isConstantValue(TR, store.getStoredPointer())) {
2791 llvm::errs() << " constant instruction as store operand is inactive"
2792 << *op << "\n";
2793 return true;
2794 }
2795 if (inactArg) {
2796 inactArg->insert(store.getStoredValue());
2797 inactArg->insert(store.getStoredPointer());
2798 }
2799 return false;
2800 }
2801
2802 if (isa<LLVM::MemcpyOp, LLVM::MemmoveOp>(op)) {
2803 // if either src or dst is inactive, there cannot be a transfer of active
2804 // values and thus the store is inactive
2805 if (isConstantValue(TR, op->getOperand(0)) ||
2806 isConstantValue(TR, op->getOperand(1))) {
2808 llvm::errs() << " constant instruction as memtransfer " << *op << "\n";
2809 return true;
2810 }
2811 if (inactArg) {
2812 inactArg->insert(op->getOperand(0));
2813 inactArg->insert(op->getOperand(1));
2814 }
2815 return false;
2816 }
2817
2818 if (auto call = dyn_cast<CallOpInterface>(op)) {
2819 if (op->hasAttr("enzyme_inactive")) {
2820 return true;
2821 }
2822 // Calls to print/assert/cxa guard are definitionally inactive
2823 Operation *called = getFunctionFromCall(call);
2824 StringRef funcName =
2825 called ? cast<SymbolOpInterface>(called).getName() : "";
2826
2827 if (called && called->hasAttr("enzyme_inactive")) {
2828 return true;
2829 }
2830 if (funcName == "free" || funcName == "_ZdlPv" || funcName == "_ZdlPvm" ||
2831 funcName == "munmap") {
2832 return true;
2833 }
2834
2835 auto dName = llvm::demangle(funcName.str());
2836 for (auto FuncName : DemangledKnownInactiveFunctionsStartingWith) {
2837 if (startsWith(dName, FuncName)) {
2838 return true;
2839 }
2840 }
2841
2842 for (auto FuncName : KnownInactiveFunctionsStartingWith) {
2843 if (startsWith(funcName, FuncName)) {
2844 return true;
2845 }
2846 }
2847
2848 for (auto FuncName : KnownInactiveFunctionsContains) {
2849 if (funcName.contains(FuncName)) {
2850 return true;
2851 }
2852 }
2853
2854 if (KnownInactiveFunctions.count(funcName.str()) ||
2855 MPIInactiveCommAllocators.find(funcName.str()) !=
2858 llvm::errs() << "constant(" << (int)directions
2859 << ") up-knowninactivecall " << *op << "\n";
2860 return true;
2861 }
2862
2863 //
2864 // TODO support this as a pass/configuration option.
2865 //
2866 // If requesting empty unknown functions to be considered inactive, abide
2867 // by those rules
2868 // if (called && EnzymeEmptyFnInactive && called->empty() &&
2869 // !hasMetadata(called, "enzyme_gradient") &&
2870 // !hasMetadata(called, "enzyme_derivative") &&
2871 // !isAllocationFunction(funcName, TLI) &&
2872 // !isDeallocationFunction(funcName, TLI) && !isa<IntrinsicInst>(op)) {
2873 // if (EnzymePrintActivity)
2874 // llvm::errs() << "constant(" << (int)directions << ") up-emptyconst "
2875 // << *inst << "\n";
2876 // return true;
2877 // }
2878 Value callVal = dyn_cast<Value>(call.getCallableForCallee());
2879 if (callVal)
2880 if (isConstantValue(TR, callVal)) {
2882 llvm::errs() << "constant(" << (int)directions << ") up-constfn "
2883 << *op << " - " << callVal << "\n";
2884 return true;
2885 }
2886 }
2887
2888 if (auto view = dyn_cast<ViewLikeOpInterface>(op)) {
2889 // Only the source of a view can make it active.
2890 if (isConstantValue(TR, view.getViewSource())) {
2892 llvm::errs() << "constant(" << (int)directions << ") up-view " << *op
2893 << "\n";
2894 return true;
2895 }
2896 if (inactArg) {
2897 inactArg->insert(view.getViewSource());
2898 }
2899 return false;
2900 }
2901
2902 //
2903 // TODO: better support for known function calls. Ideally, they should become
2904 // operations, but we also need parity with LLVM-enzyme.
2905 //
2906 // if (auto ci = dyn_cast<CallInst>(inst)) {
2907 // bool seenuse = false;
2908
2909 // propagateArgumentInformation(TLI, *ci, [&](Value *a) {
2910 // if (!isConstantValue(TR, a)) {
2911 // seenuse = true;
2912 // if (EnzymePrintActivity)
2913 // llvm::errs() << "nonconstant(" << (int)directions << ") up-call "
2914 // << *inst << " op " << *a << "\n";
2915 // return true;
2916 // }
2917 // return false;
2918 // });
2919 // if (EnzymeGlobalActivity) {
2920 // if (!ci->onlyAccessesArgMemory() && !ci->doesNotAccessMemory()) {
2921 // bool legalUse = false;
2922
2923 // StringRef funcName = getFuncNameFromCall(ci);
2924
2925 // if (funcName == "") {
2926 // } else if (isMemFreeLibMFunction(funcName) ||
2927 // isDebugFunction(ci->getCalledFunction()) ||
2928 // isCertainPrint(funcName) ||
2929 // isAllocationFunction(funcName, TLI) ||
2930 // isDeallocationFunction(funcName, TLI)) {
2931 // legalUse = true;
2932 // }
2933
2934 // if (!legalUse) {
2935 // if (EnzymePrintActivity)
2936 // llvm::errs() << "nonconstant(" << (int)directions << ") up-global
2937 // "
2938 // << *inst << "\n";
2939 // seenuse = true;
2940 // }
2941 // }
2942 // }
2943
2944 // if (!seenuse) {
2945 // if (EnzymePrintActivity)
2946 // llvm::errs() << "constant(" << (int)directions << ") up-call:" <<
2947 // *inst
2948 // << "\n";
2949 // return true;
2950 // }
2951 // return !seenuse;
2952 // }
2953
2954 if (auto si = dyn_cast<LLVM::SelectOp>(op)) {
2955
2956 if (isConstantValue(TR, si.getTrueValue()) &&
2957 isConstantValue(TR, si.getFalseValue())) {
2958
2960 llvm::errs() << "constant(" << (int)directions << ") up-sel:" << *op
2961 << "\n";
2962 return true;
2963 }
2964 if (inactArg) {
2965 inactArg->insert(si.getTrueValue());
2966 inactArg->insert(si.getFalseValue());
2967 }
2968 return false;
2969 }
2970
2971 if (!resultNo) {
2972 for (Value a : op->getOperands()) {
2973 bool hypval = isConstantValue(TR, a);
2974 if (!hypval) {
2976 llvm::errs() << "nonconstant(" << (int)directions << ") up-inst "
2977 << *op << " op " << a << "\n";
2978 if (inactArg) {
2979 inactArg->insert(a);
2980 }
2981 return false;
2982 }
2983 }
2984 // Conservatively check all top-level operations nested in the region,
2985 // there is recursion there.
2986 for (Region &region : op->getRegions()) {
2987 for (Block &block : region) {
2988 // XXX: We think that we don't need to check block arguments here
2989 // because values flow into them either from operands of the parent op
2990 // or from the op itself.
2991 for (Operation &nested : block) {
2992 // No need to check the results, even if they may be active,
2993 // because in absence of resultNo, we are checking for the
2994 // entire op being inactive not individual values.
2995 //
2996 // // The loop _operation_ is inactive, but the result is, just
2997 // // like the GEP inside it.
2998 // %r = scf.for %i.. {
2999 // // The GEP operation is not active, but the result is.
3000 // %active_r = llvm.gep ... %active_operand
3001 // scf.yield %active_r
3002 // }
3003 if (!isConstantOperation(TR, &nested)) {
3004 // TODO set inactArg here, except with constant operand.
3005 // assert(!inactArg);
3007 llvm::errs() << "nonconstant(" << (int)directions
3008 << ") up-inst-op " << *op << " sub-op " << nested
3009 << "\n";
3010 return false;
3011 }
3012 }
3013 }
3014 }
3015 } else {
3016 for (auto value : getPotentialIncomingValues(op->getResult(*resultNo))) {
3017 if (!isConstantValue(TR, value)) {
3019 llvm::errs() << "nonconstant(" << (int)directions << ") up-inst "
3020 << *op << " value " << value << "\n";
3021 if (inactArg)
3022 inactArg->insert(value);
3023 return false;
3024 }
3025 }
3026 }
3027
3029 llvm::errs() << "constant(" << (int)directions << ") up-inst:" << *op
3030 << "\n";
3031 return true;
3032}
3033
3034/// Is the value free of any active uses
3036 MTypeResults const &TR, Value val, UseActivity PUA, Operation **FoundInst) {
3037 assert(directions & DOWN);
3038 // Must be an analyzer only searching down, unless used outside
3039 // assert(directions == DOWN);
3040
3041 // To ensure we can call down
3042
3044 llvm::errs() << " <Value USESEARCH" << (int)directions << ">" << val
3045 << " UA=" << (int)PUA << "\n";
3046
3047 bool seenuse = false;
3048 // user, predecessor
3049 std::deque<std::tuple<Operation *, Value, UseActivity>> todo;
3050 for (Operation *a : val.getUsers()) {
3051 todo.push_back(std::make_tuple(a, val, PUA));
3052 }
3053 std::set<std::tuple<Operation *, Value, UseActivity>> done = {};
3054
3055 llvm::SmallPtrSet<Value, 1> AllocaSet;
3056
3057 if (val.getDefiningOp<LLVM::AllocaOp>())
3058 AllocaSet.insert(val);
3059
3060 // if (PUA == UseActivity::None && isAllocationCall(val, TLI))
3061 // AllocaSet.insert(val);
3062
3063 while (todo.size()) {
3064 Operation *a;
3065 Value parent;
3066 UseActivity UA;
3067 auto tuple = todo.front();
3068 todo.pop_front();
3069 if (done.count(tuple))
3070 continue;
3071 done.insert(tuple);
3072 std::tie(a, parent, UA) = tuple;
3073
3074 if (auto LI = dyn_cast<LLVM::LoadOp>(a)) {
3075 if (UA == UseActivity::OnlyStores)
3076 continue;
3077 // if (UA == UseActivity::OnlyNonPointerStores ||
3078 // UA == UseActivity::AllStores) {
3079 // if (!TR.query(LI)[{-1}].isPossiblePointer())
3080 // continue;
3081 // }
3082 }
3083
3084 if (UA != UseActivity::AllStores) {
3085 if (auto ifaceOp = dyn_cast<enzyme::ActivityOpInterface>(a)) {
3086 bool allInactive = true;
3087 for (OpOperand &operand : a->getOpOperands()) {
3088 if (parent == operand.get() &&
3089 !ifaceOp.isArgInactive(operand.getOperandNumber())) {
3090 allInactive = false;
3091 break;
3092 }
3093 }
3094 if (allInactive)
3095 continue;
3096 }
3097 }
3098
3100 llvm::errs() << " considering use of " << val << " - " << *a << "\n";
3101
3102 // Only ignore stores to the operand, not storing the operand
3103 // somewhere
3104 if (auto SI = dyn_cast<LLVM::StoreOp>(a)) {
3105 if (SI.getValue() != parent) {
3106 if (UA == UseActivity::OnlyLoads) {
3107 continue;
3108 }
3109 if (UA != UseActivity::AllStores &&
3110 (ConstantValues.count(SI.getValue()) ||
3111 // FIXME: this was a llvm::ConstantInt, should not be a hardcoded
3112 // assumption that ints are not active.
3113 (SI.getValue().getDefiningOp<LLVM::ConstantOp>() &&
3114 isa<IntegerType>(SI.getValue().getType()))))
3115 continue;
3116 if (UA == UseActivity::None) {
3117 // If storing into itself, all potential uses are taken care of
3118 // elsewhere in the recursion.
3119 bool shouldContinue = true;
3120 SmallVector<Value, 1> vtodo = {SI.getValue()};
3121 llvm::SmallPtrSet<Value, 1> seen;
3122 llvm::SmallPtrSet<Value, 1> newAllocaSet;
3123 while (vtodo.size()) {
3124 auto TmpOrig = vtodo.back();
3125 vtodo.pop_back();
3126 if (seen.count(TmpOrig))
3127 continue;
3128 seen.insert(TmpOrig);
3129 if (AllocaSet.count(TmpOrig)) {
3130 continue;
3131 }
3132 if (TmpOrig.getDefiningOp<LLVM::AllocaOp>()) {
3133 newAllocaSet.insert(TmpOrig);
3134 continue;
3135 }
3136 // if (isAllocationCall(TmpOrig, TLI)) {
3137 // newAllocaSet.insert(TmpOrig);
3138 // continue;
3139 // }
3140 if (isa_and_nonnull<LLVM::UndefOp, LLVM::ConstantOp>(
3141 TmpOrig.getDefiningOp())) {
3142 continue;
3143 }
3144 if (auto LI = TmpOrig.getDefiningOp<LLVM::LoadOp>()) {
3145 vtodo.push_back(LI.getAddr());
3146 continue;
3147 }
3148 //
3149 // TODO: handle globals in MLIResque way.
3150 //
3151 // if (auto CD = dyn_cast<ConstantDataSequential>(TmpOrig)) {
3152 // for (size_t i = 0, len = CD->getNumElements(); i < len; i++)
3153 // vtodo.push_back(CD->getElementAsConstant(i));
3154 // continue;
3155 // }
3156 // if (auto CD = dyn_cast<ConstantAggregate>(TmpOrig)) {
3157 // for (size_t i = 0, len = CD->getNumOperands(); i < len; i++)
3158 // vtodo.push_back(CD->getOperand(i));
3159 // continue;
3160 // }
3161 // if (auto GV = dyn_cast<GlobalVariable>(TmpOrig)) {
3162 // // If operating under the assumption globals are inactive
3163 // unless
3164 // // explicitly marked as active, this is inactive
3165 // if (!hasMetadata(GV, "enzyme_shadow") &&
3166 // EnzymeNonmarkedGlobalsInactive) {
3167 // continue;
3168 // }
3169 // if (GV->getName().contains("enzyme_const") ||
3170 // InactiveGlobals.count(GV->getName().str())) {
3171 // continue;
3172 // }
3173 // }
3174 // auto TmpOrig_2 = getUnderlyingObject(TmpOrig, 100);
3175 // if (TmpOrig != TmpOrig_2) {
3176 // vtodo.push_back(TmpOrig_2);
3177 // continue;
3178 // }
3180 llvm::errs() << " -- cannot continuing indirect store from"
3181 << val << " due to " << TmpOrig << "\n";
3182 shouldContinue = false;
3183 break;
3184 }
3185 if (shouldContinue) {
3187 llvm::errs() << " -- continuing indirect store from " << val
3188 << " into:\n";
3189 done.insert(std::make_tuple(SI.getOperation(), SI.getValue(), UA));
3190 for (Value TmpOrig : newAllocaSet) {
3191
3192 for (Operation *a : TmpOrig.getUsers()) {
3193 todo.push_back(std::make_tuple(a, TmpOrig, UA));
3195 llvm::errs() << " ** " << *a << "\n";
3196 }
3197 AllocaSet.insert(TmpOrig);
3198 shouldContinue = true;
3199 }
3200 continue;
3201 }
3202 }
3203 }
3204 if (SI.getAddr() != parent) {
3205 Value TmpOrig = SI.getAddr();
3206 // If storing into itself, all potential uses are taken care of
3207 // elsewhere in the recursion.
3208 bool shouldContinue = false;
3209 while (1) {
3210 if (AllocaSet.count(TmpOrig)) {
3211 shouldContinue = true;
3212 break;
3213 }
3214 if (TmpOrig.getDefiningOp<LLVM::AllocaOp>()) {
3215 done.insert(std::make_tuple(SI.getOperation(), SI.getAddr(), UA));
3216 for (const auto a : TmpOrig.getUsers()) {
3217 todo.push_back(std::make_tuple(a, TmpOrig, UA));
3218 }
3219 AllocaSet.insert(TmpOrig);
3220 shouldContinue = true;
3221 break;
3222 }
3223 if (PUA == UseActivity::None) {
3224 if (auto LI = TmpOrig.getDefiningOp<LLVM::LoadOp>()) {
3225 TmpOrig = LI.getAddr();
3226 continue;
3227 }
3228 // if (isAllocationCall(TmpOrig, TLI)) {
3229 // done.insert(
3230 // std::make_tuple(SI.getOperation(), SI.getAddr(), UA));
3231 // for (Operation *a : TmpOrig.getUsers()) {
3232 // todo.push_back(std::make_tuple(a, TmpOrig, UA));
3233 // }
3234 // AllocaSet.insert(TmpOrig);
3235 // shouldContinue = true;
3236 // break;
3237 // }
3238 }
3239 // auto TmpOrig_2 =
3240 // #if LLVM_VERSION_MAJOR >= 12
3241 // getUnderlyingObject(TmpOrig, 100);
3242 // #else
3243 // GetUnderlyingObject(
3244 // TmpOrig,
3245 // TR.getFunction()->getParent()->getDataLayout(),
3246 // 100);
3247 // #endif
3248 // if (TmpOrig != TmpOrig_2) {
3249 // TmpOrig = TmpOrig_2;
3250 // continue;
3251 // }
3252 break;
3253 }
3254 if (shouldContinue) {
3256 llvm::errs() << " -- continuing indirect store2 from " << val
3257 << " via " << TmpOrig << "\n";
3258 continue;
3259 }
3260 }
3261 if (PUA == UseActivity::OnlyLoads) {
3262 // auto TmpOrig =
3263 // #if LLVM_VERSION_MAJOR >= 12
3264 // getUnderlyingObject(SI->getPointerOperand(), 100);
3265 // #else
3266 // GetUnderlyingObject(SI->getPointerOperand(),
3267 // TR.getFunction()->getParent()->getDataLayout(),
3268 // 100);
3269 // #endif
3270 // if (TmpOrig == val) {
3271 // continue;
3272 // }
3273 }
3274 }
3275
3276 // if (!isa<Instruction>(a)) {
3277 // if (auto CE = dyn_cast<ConstantExpr>(a)) {
3278 // for (auto u : CE->users()) {
3279 // todo.push_back(std::make_tuple(u, (Value *)CE, UA));
3280 // }
3281 // continue;
3282 // }
3283 // if (isa<ConstantData>(a)) {
3284 // continue;
3285 // }
3286
3287 // if (EnzymePrintActivity)
3288 // llvm::errs() << " unknown non instruction use of " << *val << "
3289 // - "
3290 // << *a << "\n";
3291 // return false;
3292 // }
3293
3294 if (isa<LLVM::AllocaOp>(a)) {
3296 llvm::errs() << "found constant(" << (int)directions
3297 << ") allocainst use:" << val << " user " << *a << "\n";
3298 continue;
3299 }
3300
3301 //
3302 // TODO: this should not happen in valid MLIR...
3303 //
3304 // if this instruction is in a different function, conservatively assume
3305 // it is active
3306 // Function *InstF = cast<Instruction>(a)->getParent()->getParent();
3307 // while (PPC.CloneOrigin.find(InstF) != PPC.CloneOrigin.end())
3308 // InstF = PPC.CloneOrigin[InstF];
3309
3310 // Function *F = TR.getFunction();
3311 // while (PPC.CloneOrigin.find(F) != PPC.CloneOrigin.end())
3312 // F = PPC.CloneOrigin[F];
3313
3314 // if (InstF != F) {
3315 // if (EnzymePrintActivity)
3316 // llvm::errs() << "found use in different function(" << (int)directions
3317 // << ") val:" << *val << " user " << *a << " in "
3318 // << InstF->getName() << "@" << InstF
3319 // << " self: " << F->getName() << "@" << F << "\n";
3320 // return false;
3321 // }
3322 // if (cast<Instruction>(a)->getParent()->getParent() != TR.getFunction())
3323 // continue;
3324
3325 // This use is only active if specified
3326 if (UA != UseActivity::AllStores) {
3327 if (auto termUsers = getPotentialTerminatorUsers(a, parent)) {
3328 for (auto post : *termUsers) {
3329 for (Operation *postUser : post.getUsers()) {
3330 todo.push_back(std::make_tuple(postUser, post, UA));
3331 }
3332 }
3333 continue;
3334 }
3335 if (isFunctionReturn(a)) {
3336 bool allConstant = true;
3337 assert(a->getOperands().size() == ActiveReturns.size());
3338 for (auto &&[retval, act] :
3339 llvm::zip(a->getOperands(), ActiveReturns)) {
3340 if (retval != parent)
3341 continue;
3342 if (act == DIFFE_TYPE::CONSTANT)
3343 continue;
3344 allConstant = false;
3345 break;
3346 }
3347 if (allConstant) {
3348 continue;
3349 } else {
3350 return false;
3351 }
3352 }
3353 }
3354
3355 if (isa<LLVM::MemcpyOp, LLVM::MemmoveOp>(a)) {
3356 //
3357 // TODO: see how this works with MLIR global constant operation approach
3358 //
3359 // copies of constant string data do not impact activity.
3360 // if (auto cexpr = dyn_cast<ConstantExpr>(call->getArgOperand(1))) {
3361 // if (cexpr->getOpcode() == Instruction::GetElementPtr) {
3362 // if (auto GV = dyn_cast<GlobalVariable>(cexpr->getOperand(0))) {
3363 // if (GV->hasInitializer() && GV->isConstant()) {
3364 // if (auto CDA =
3365 // dyn_cast<ConstantDataArray>(GV->getInitializer())) {
3366 // if (CDA->getType()->getElementType()->isIntegerTy(8))
3367 // continue;
3368 // }
3369 // }
3370 // }
3371 // }
3372 // }
3373
3374 // Only need to care about loads from
3375 if (UA == UseActivity::OnlyLoads && a->getOperand(1) != parent)
3376 continue;
3377
3378 // Only need to care about store from
3379 if (a->getOperand(0) != parent) {
3380 if (UA == UseActivity::OnlyStores)
3381 continue;
3382 else if (UA == UseActivity::OnlyNonPointerStores ||
3383 UA == UseActivity::AllStores) {
3384 // todo can change this to query either -1 (all mem) or 0..size
3385 // (if size of copy is const)
3386 // if (!TR.query(call->getArgOperand(1))[{-1,
3387 // -1}].isPossiblePointer())
3388 // continue;
3389 }
3390 }
3391
3392 bool shouldContinue = false;
3393 if (UA != UseActivity::AllStores)
3394 for (int arg = 0; arg < 2; arg++)
3395 if (a->getOperand(arg) != parent &&
3396 (arg == 0 || (PUA == UseActivity::None))) {
3397 Value TmpOrig = a->getOperand(arg);
3398 while (1) {
3399 if (AllocaSet.count(TmpOrig)) {
3400 shouldContinue = true;
3401 break;
3402 }
3403 if (TmpOrig.getDefiningOp<LLVM::AllocaOp>()) {
3404 done.insert(std::make_tuple(a, a->getOperand(arg), UA));
3405 for (Operation *a : TmpOrig.getUsers()) {
3406 todo.push_back(std::make_tuple(a, TmpOrig, UA));
3407 }
3408 AllocaSet.insert(TmpOrig);
3409 shouldContinue = true;
3410 break;
3411 }
3412 if (PUA == UseActivity::None) {
3413 if (auto LI = TmpOrig.getDefiningOp<LLVM::LoadOp>()) {
3414 TmpOrig = LI.getAddr();
3415 continue;
3416 }
3417 // if (isAllocationCall(TmpOrig, TLI)) {
3418 // done.insert(std::make_tuple((User *)call,
3419 // call->getArgOperand(arg), UA));
3420 // for (const auto a : TmpOrig->users()) {
3421 // todo.push_back(std::make_tuple(a, TmpOrig, UA));
3422 // }
3423 // AllocaSet.insert(TmpOrig);
3424 // shouldContinue = true;
3425 // break;
3426 // }
3427 }
3428 // auto TmpOrig_2 = getUnderlyingObject(TmpOrig, 100);
3429 // if (TmpOrig != TmpOrig_2) {
3430 // TmpOrig = TmpOrig_2;
3431 // continue;
3432 // }
3433 break;
3434 }
3435 if (shouldContinue)
3436 break;
3437 }
3438
3439 if (shouldContinue)
3440 continue;
3441 }
3442
3443 if (auto call = dyn_cast<CallOpInterface>(a)) {
3444 bool ConstantArg = isFunctionArgumentConstant(call, parent);
3445 if (ConstantArg && UA != UseActivity::AllStores) {
3446 if (EnzymePrintActivity) {
3447 llvm::errs() << "Value found constant callinst use:" << val
3448 << " user " << *call << "\n";
3449 }
3450 continue;
3451 }
3452
3453 if (Operation *F = getFunctionFromCall(call)) {
3454 if (UA == UseActivity::AllStores &&
3455 cast<SymbolOpInterface>(F).getName() == "julia.write_barrier")
3456 continue;
3457
3458 } else if (PUA == UseActivity::None || PUA == UseActivity::OnlyStores) {
3459 // If calling a function derived from an alloca of this value,
3460 // the function is only active if the function stored into
3461 // the allocation is active (all functions not explicitly marked
3462 // inactive), or one of the args to the call is active
3463 Value operand = dyn_cast<Value>(call.getCallableForCallee());
3464 assert(operand);
3465
3466 bool toContinue = false;
3467 if (operand.getDefiningOp<LLVM::LoadOp>()) {
3468 bool legal = true;
3469
3470 for (unsigned i = 0; i < call.getArgOperands().size(); ++i) {
3471 Value a = call.getArgOperands()[i];
3472
3473 // FIXME: yet another ingrained assumption that integers cannot be
3474 // active.
3475 llvm::APInt intValue;
3476 if (matchPattern(a, m_ConstantInt(&intValue)))
3477 continue;
3478
3479 // Value ptr = a;
3480 bool subValue = false;
3481 // while (ptr) {
3482 // auto TmpOrig2 = getUnderlyingObject(ptr, 100);
3483 // if (AllocaSet.count(TmpOrig2)) {
3484 // subValue = true;
3485 // break;
3486 // }
3487 // if (isa<AllocaInst>(TmpOrig2)) {
3488 // done.insert(std::make_tuple((User *)call, a, UA));
3489 // for (const auto a : TmpOrig2->users()) {
3490 // todo.push_back(std::make_tuple(a, TmpOrig2, UA));
3491 // }
3492 // AllocaSet.insert(TmpOrig2);
3493 // subValue = true;
3494 // break;
3495 // }
3496
3497 // if (PUA == UseActivity::None) {
3498 // if (isAllocationCall(TmpOrig2, TLI)) {
3499 // done.insert(std::make_tuple((User *)call, a, UA));
3500 // for (const auto a : TmpOrig2->users()) {
3501 // todo.push_back(std::make_tuple(a, TmpOrig2, UA));
3502 // }
3503 // AllocaSet.insert(TmpOrig2);
3504 // subValue = true;
3505 // break;
3506 // }
3507 // if (auto L = dyn_cast<LoadInst>(TmpOrig2)) {
3508 // ptr = L->getPointerOperand();
3509 // } else
3510 // ptr = nullptr;
3511 // } else
3512 // ptr = nullptr;
3513 // }
3514 if (subValue)
3515 continue;
3516 legal = false;
3517 break;
3518 }
3519 if (legal) {
3520 toContinue = true;
3521 break;
3522 }
3523 }
3524 if (toContinue)
3525 continue;
3526 }
3527 }
3528
3529 // For an inbound gep, args which are not the pointer being offset
3530 // are not used in an active way by definition.
3531 if (auto gep = dyn_cast<LLVM::GEPOp>(a)) {
3532 // if (gep->isInBounds() && gep->getPointerOperand() != parent)
3533 // continue;
3534 }
3535
3536 //
3537 // TODO: in MLIR, a user is always an operation
3538 //
3539 // If this doesn't write to memory this can only be an active use
3540 // if its return is used in an active way, therefore add this to
3541 // the list of users to analyze
3542 if (Operation *I = a) {
3543 if (notForAnalysis.count(I->getBlock())) {
3544 // TODO(PR #904): replace the "EnzymePrintActivity" flag with LLVM_DEBUG
3545 if (EnzymePrintActivity) {
3546 llvm::errs() << "Value found constant unreachable inst use:" << val
3547 << " user " << *I << "\n";
3548 }
3549 continue;
3550 }
3551 if (UA != UseActivity::AllStores && ConstantOperations.count(I)) {
3552 if (llvm::all_of(I->getResults(), [&](Value val) {
3553 return isa<LLVM::LLVMVoidType, mlir::TokenType>(val.getType()) ||
3554 ConstantValues.count(val);
3555 })) {
3556 if (EnzymePrintActivity) {
3557 llvm::errs() << "Value found constant inst use:" << val << " user "
3558 << *I << "\n";
3559 }
3560 continue;
3561 }
3562 UseActivity NU = UA;
3563 if (UA == UseActivity::OnlyLoads || UA == UseActivity::OnlyStores ||
3564 UA == UseActivity::OnlyNonPointerStores) {
3565 if (!isa<
3566 // clang-format off
3567 ViewLikeOpInterface,
3568 // Integer binary ops.
3569 LLVM::AddOp,
3570 LLVM::SubOp,
3571 LLVM::MulOp,
3572 LLVM::UDivOp,
3573 LLVM::SDivOp,
3574 LLVM::URemOp,
3575 LLVM::SRemOp,
3576 LLVM::AndOp,
3577 LLVM::OrOp,
3578 LLVM::XOrOp,
3579 LLVM::ShlOp,
3580 LLVM::LShrOp,
3581 LLVM::AShrOp,
3582 // Float binary ops.
3583 LLVM::FAddOp,
3584 LLVM::FSubOp,
3585 LLVM::FMulOp,
3586 LLVM::FDivOp,
3587 LLVM::FRemOp,
3588 LLVM::FNegOp,
3589 // Cast op
3590 LLVM::IntToPtrOp,
3591 LLVM::PtrToIntOp,
3592 LLVM::SExtOp,
3593 LLVM::ZExtOp,
3594 LLVM::TruncOp,
3595 LLVM::FPExtOp,
3596 LLVM::FPTruncOp
3597 // clang-format on
3598 >(I)) {
3599 NU = UseActivity::None;
3600 }
3601 }
3602
3603 for (Value result : I->getResults()) {
3604 for (Operation *u : result.getUsers()) {
3605 todo.push_back(std::make_tuple(u, result, NU));
3606 }
3607 }
3608 continue;
3609 }
3610
3611 if (isReadOnly(I)) {
3612 // if (TR.query(I)[{-1}].isIntegral()) {
3613 // continue;
3614 //}
3615 UseActivity NU = UA;
3616 if (UA == UseActivity::OnlyLoads || UA == UseActivity::OnlyStores ||
3617 UA == UseActivity::OnlyNonPointerStores) {
3618 // if (!isa<PHINode>(I) && !isa<CastInst>(I) &&
3619 // !isa<GetElementPtrInst>(I) && !isa<BinaryOperator>(I))
3620 NU = UseActivity::None;
3621 }
3622
3623 for (Value result : I->getResults()) {
3624 for (Operation *u : result.getUsers()) {
3625 todo.push_back(std::make_tuple(u, result, NU));
3626 }
3627 }
3628 continue;
3629 }
3630
3631 if (FoundInst)
3632 *FoundInst = I;
3633 }
3634
3636 llvm::errs() << "Value nonconstant inst (uses):" << val << " user " << *a
3637 << "\n";
3638 seenuse = true;
3639 break;
3640 }
3641
3643 llvm::errs() << " </Value USESEARCH" << (int)directions
3644 << " const=" << (!seenuse) << ">" << val << "\n";
3645 return !seenuse;
3646}
3647
3648/// Is the value potentially actively returned or stored
3650 MTypeResults const &TR, Value val, bool outside) {
3651 // Must be an analyzer only searching down
3652 if (!outside)
3653 assert(directions == DOWN);
3654
3655 bool ignoreStoresInto = true;
3656 auto key = std::make_pair(ignoreStoresInto, val);
3657 if (StoredOrReturnedCache.find(key) != StoredOrReturnedCache.end()) {
3658 return StoredOrReturnedCache[key];
3659 }
3660
3662 llvm::errs() << " <ASOR" << (int)directions
3663 << " ignoreStoresinto=" << ignoreStoresInto << ">" << val
3664 << "\n";
3665
3666 StoredOrReturnedCache[key] = false;
3667
3668 for (Operation *a : val.getUsers()) {
3669 if (isa<LLVM::AllocaOp>(a)) {
3670 continue;
3671 }
3672 // Loading a value prevents its pointer from being captured
3673 if (isa<LLVM::LoadOp>(a)) {
3674 continue;
3675 }
3676
3677 if (auto ifaceOp = dyn_cast<enzyme::ActivityOpInterface>(a)) {
3678 bool allInactive = true;
3679 for (OpOperand &operand : a->getOpOperands()) {
3680 if (operand.get() == val &&
3681 !ifaceOp.isArgInactive(operand.getOperandNumber())) {
3682 allInactive = false;
3683 break;
3684 }
3685 }
3686 if (allInactive)
3687 continue;
3688 }
3689
3690 if (auto termUsers = getPotentialTerminatorUsers(a, val)) {
3691 for (auto post : *termUsers)
3692 if (isValueActivelyStoredOrReturned(TR, post, outside)) {
3693 return StoredOrReturnedCache[key] = true;
3694 }
3695 return false;
3696 }
3697 if (isFunctionReturn(a)) {
3698 bool allConstant = true;
3699 assert(a->getOperands().size() == ActiveReturns.size());
3700 for (auto &&[retval, act] : llvm::zip(a->getOperands(), ActiveReturns)) {
3701 if (retval != val)
3702 continue;
3703 if (act == DIFFE_TYPE::CONSTANT)
3704 continue;
3705 allConstant = false;
3706 break;
3707 }
3708 if (allConstant)
3709 continue;
3710
3712 llvm::errs() << " </ASOR" << (int)directions
3713 << " ignoreStoresInto=" << ignoreStoresInto << ">"
3714 << " active from-ret>" << val << "\n";
3715 StoredOrReturnedCache[key] = true;
3716 return true;
3717 }
3718
3719 if (auto call = dyn_cast<CallOpInterface>(a)) {
3720 // if (!couldFunctionArgumentCapture(call, val)) {
3721 // continue;
3722 // }
3723 bool ConstantArg = isFunctionArgumentConstant(call, val);
3724 if (ConstantArg) {
3725 continue;
3726 }
3727 }
3728
3729 if (auto SI = dyn_cast<enzyme::StoreLikeInterface>(a)) {
3730 // If we are being stored into, not storing this value
3731 // this case can be skipped
3732 if (SI.getStoredValue() != val) {
3733 if (!ignoreStoresInto) {
3734 // Active value stored into `val` (the pointer): `val` is active.
3735 if (!isConstantValue(TR, SI.getStoredValue())) {
3736 StoredOrReturnedCache[key] = true;
3738 llvm::errs() << " </ASOR" << (int)directions
3739 << " ignoreStoresInto=" << ignoreStoresInto
3740 << " active from-store>" << val
3741 << " store into=" << *SI << "\n";
3742 return true;
3743 }
3744 }
3745 continue;
3746 } else {
3747 // `val` is stored into active memory: active.
3748 if (!isConstantValue(TR, SI.getStoredPointer())) {
3749 StoredOrReturnedCache[key] = true;
3751 llvm::errs() << " </ASOR" << (int)directions
3752 << " ignoreStoresInto=" << ignoreStoresInto
3753 << " active from-store>" << val << " store=" << *SI
3754 << "\n";
3755 return true;
3756 }
3757 continue;
3758 }
3759 }
3760
3761 //
3762 // TODO: in MLIR, users are always operations
3763 //
3764 if (Operation *inst = a) {
3765 if (!mayWriteToMemory(inst) /*||
3766 (isa<CallInst>(inst) && AA.onlyReadsMemory(cast<CallInst>(inst)))*/) {
3767 // // if not written to memory and returning a known constant, this
3768 // // cannot be actively returned/stored
3769 // if (inst->getParent()->getParent() == TR.getFunction() &&
3770 // isConstantValue(TR, a)) {
3771 // continue;
3772 // }
3773
3774 // if not written to memory and returning a value itself
3775 // not actively stored or returned, this is not actively
3776 // stored or returned
3777 if (!llvm::all_of(a->getResults(), [&](Value val) {
3778 return isValueActivelyStoredOrReturned(TR, val, outside);
3779 })) {
3780 continue;
3781 }
3782 }
3783 }
3784
3785 // if (isAllocationCall(a, TLI)) {
3786 // // if not written to memory and returning a known constant, this
3787 // // cannot be actively returned/stored
3788 // if (isConstantValue(TR, a)) {
3789 // continue;
3790 // }
3791 // // if not written to memory and returning a value itself
3792 // // not actively stored or returned, this is not actively
3793 // // stored or returned
3794 // if (!isValueActivelyStoredOrReturned(TR, a, outside)) {
3795 // continue;
3796 // }
3797 // } else if (isDeallocationCall(a, TLI)) {
3798 // // freeing memory never counts
3799 // continue;
3800 // }
3801 // fallback and conservatively assume that if the value is written to
3802 // it is written to active memory
3803 // TODO handle more memory instructions above to be less conservative
3804
3806 llvm::errs() << " </ASOR" << (int)directions
3807 << " ignoreStoresInto=" << ignoreStoresInto
3808 << " active from-unknown>" << val << " - use=" << *a << "\n";
3809 return StoredOrReturnedCache[key] = true;
3810 }
3811
3813 llvm::errs() << " </ASOR" << (int)directions
3814 << " ignoreStoresInto=" << ignoreStoresInto << " inactive>"
3815 << val << "\n";
3816 return false;
3817}
3818
3819void mlir::enzyme::ActivityAnalyzer::InsertConstantOperation(
3820 MTypeResults const &TR, Operation *I) {
3821 ConstantOperations.insert(I);
3822 auto found = ReEvaluateValueIfInactiveOp.find(I);
3823 if (found == ReEvaluateValueIfInactiveOp.end())
3824 return;
3825 auto set = std::move(ReEvaluateValueIfInactiveOp[I]);
3826 ReEvaluateValueIfInactiveOp.erase(I);
3827 for (Value toeval : set) {
3828 if (!ActiveValues.count(toeval))
3829 continue;
3830 ActiveValues.erase(toeval);
3832 llvm::errs() << " re-evaluating activity of val " << toeval
3833 << " due to inst " << *I << "\n";
3834 isConstantValue(TR, toeval);
3835 }
3836}
3837
3838void mlir::enzyme::ActivityAnalyzer::InsertConstantValue(MTypeResults const &TR,
3839 Value V) {
3840 ConstantValues.insert(V);
3841 auto found = ReEvaluateValueIfInactiveValue.find(V);
3842 if (found != ReEvaluateValueIfInactiveValue.end()) {
3843 auto set = std::move(ReEvaluateValueIfInactiveValue[V]);
3844 ReEvaluateValueIfInactiveValue.erase(V);
3845 for (auto toeval : set) {
3846 if (!ActiveValues.count(toeval))
3847 continue;
3848 ActiveValues.erase(toeval);
3850 llvm::errs() << " re-evaluating activity of val " << toeval
3851 << " due to value " << V << "\n";
3852 isConstantValue(TR, toeval);
3853 }
3854 }
3855 auto found2 = ReEvaluateOpIfInactiveValue.find(V);
3856 if (found2 != ReEvaluateOpIfInactiveValue.end()) {
3857 auto set = std::move(ReEvaluateOpIfInactiveValue[V]);
3858 ReEvaluateOpIfInactiveValue.erase(V);
3859 for (auto toeval : set) {
3860 if (!ActiveOperations.count(toeval))
3861 continue;
3862 ActiveOperations.erase(toeval);
3864 llvm::errs() << " re-evaluating activity of inst " << *toeval
3865 << " due to value " << V << "\n";
3866 isConstantOperation(TR, toeval);
3867 }
3868 }
3869}
static const std::set< std::string > KnownInactiveFunctionInsts
static const StringSet InactiveGlobals
static bool mayReadFromMemory(Operation *op)
std::optional< Value > getStored(Operation *op)
static FunctionOpInterface getFunctionIfArgument(Value value)
static Operation * getAncestorInRegion(Operation *op, Region *region)
Return an ancestor of op that resides in the given region or nullptr if there is no such ancestor.
static const char * KnownInactiveFunctionsContains[]
static bool isFunctionReturn(Operation *op)
static std::optional< SmallVector< Value > > getPotentialTerminatorUsers(Operation *op, Value parent)
bool cached
static bool mayAllocateMemory(Operation *op)
static const char * KnownInactiveFunctionsStartingWith[]
static const std::set< std::string > KnownInactiveFunctions
static const char * DemangledKnownInactiveFunctionsStartingWith[]
static bool isReadOnly(Operation *op)
static bool mayWriteToMemory(Operation *op)
static SmallVector< Value > getPotentialIncomingValues(OpResult res)
bool isValuePotentiallyUsedAsPointer(llvm::Value *val)
static void allFollowersOf(Operation *op, function_ref< WalkResult(Operation *)> f)
Call f on all operations that may be executed after op.
static Operation * getFunctionFromCall(CallOpInterface iface)
const llvm::StringMap< size_t > MPIInactiveCommAllocators
static const unsigned constantIntrinsics[]
llvm::cl::opt< bool > EnzymePrintActivity
static bool startsWith(llvm::StringRef string, llvm::StringRef prefix)
Definition Utils.h:847
bool isIntegral() const
Whether this ConcreteType must an integer.
std::string str() const
Convert the ConcreteType to a string.
Helper class to analyze the differential activity.
bool isConstantOperation(MTypeResults const &TR, Operation *op)
Return whether this operation is known not to propagate adjoints Note that operations could return an...
bool isValueInactiveFromUsers(MTypeResults const &TR, Value val, UseActivity UA, Operation **FoundInst=nullptr)
Is the value free of any active uses.
bool isValueActivelyStoredOrReturned(MTypeResults const &TR, Value val, bool outside=false)
Is the value potentially actively returned or stored.
bool isConstantValue(MTypeResults const &TR, Value val)
Return whether this values is known not to contain derivative information, either directly or as a po...
ChangeResult meet(const AbstractSparseLattice &other) override
const DenseSet< OriginAttr > & getOrigins() const
ChangeResult meet(const AbstractDenseLattice &other) override
DataFlowActivityAnalyzer(DataFlowSolver &solver, FunctionOpInterface funcOp, ArrayRef< DIFFE_TYPE > argActivity, ArrayRef< DIFFE_TYPE > returnActivity)
ChangeResult join(const AbstractSparseLattice &other) override
const DenseSet< OriginAttr > & getOrigins() const
TypeTree query(Value) const
Definition EnzymeLogic.h:34
ConcreteType intType(size_t num, Value val, bool errIfNotFound=true, bool pointerIntSame=false) const
Definition EnzymeLogic.h:35
ChangeResult join(const AbstractDenseLattice &other)
ChangeResult merge(const SetLattice< ValueT > &other)
Value getBaseObject(Value v, bool offsetAllowed)
Definition Utils.cpp:123
bool isFunctionReturnLike(Operation *op)
void serializeSummaries(Operation *op, const enzyme::PointsToSets &p2sets, const enzyme::ForwardOriginsMap &forwardOriginsMap, ArrayRef< enzyme::ForwardOriginsLattice > returnOperandOrigins, ArrayRef< enzyme::AliasClassLattice > returnAliasClasses)
void computeSummaries(FunctionOpInterface funcOp, DataFlowSolver &solver, enzyme::PointsToSets &p2sets, enzyme::ForwardOriginsMap &forwardOriginsMap, enzyme::BackwardOriginsMap &backwardOriginsMap, SmallVectorImpl< enzyme::ForwardOriginsLattice > &returnOperandOrigins, SmallVectorImpl< enzyme::AliasClassLattice > &returnAliasClasses)
LogicalResult reverseToposortCallgraph(CallableOpInterface callee, SymbolTableCollection *symbolTable, SmallVectorImpl< CallableOpInterface > &sorted)
Starting from callee, compute a reverse (bottom-up) topological sorting of all functions transitively...
void removeSummaries(Operation *op)
void traversePointsToSets(const enzyme::AliasClassSet &start, const enzyme::PointsToSets &pointsToSets, function_ref< void(DistinctAttr)> visit)
Visit everything transitively pointed-to by any pointer in start.
LogicalResult runActivityAnnotationsForFunction(FunctionOpInterface funcOp, DataFlowSolver &solver)