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