blob: b28edafb6f8ec22b9032b32d19c6b72acfe90345 [file] [log] [blame] [edit]
//===- ActivityAnalysis.cpp - Implementation of Activity Analysis ---------===//
//
// Enzyme Project
//
// Part of the Enzyme Project, under the Apache License v2.0 with LLVM
// Exceptions. See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
// If using this code in an academic setting, please cite the following:
// @incollection{enzymeNeurips,
// title = {Instead of Rewriting Foreign Code for Machine Learning,
// Automatically Synthesize Fast Gradients},
// author = {Moses, William S. and Churavy, Valentin},
// booktitle = {Advances in Neural Information Processing Systems 33},
// year = {2020},
// note = {To appear in},
// }
//
//===----------------------------------------------------------------------===//
//
// This file contains the implementation of Activity Analysis -- an AD-specific
// analysis that deduces if a given instruction or value can impact the
// calculation of a derivative. This file consists of two mutually recurive
// functions that compute this for values and instructions, respectively.
//
//===----------------------------------------------------------------------===//
#include <cstdint>
#include <deque>
#include <llvm/Config/llvm-config.h>
#include "llvm/ADT/SmallSet.h"
#include "llvm/IR/Constants.h"
#include "llvm/IR/Function.h"
#include "llvm/IR/InstrTypes.h"
#include "llvm/IR/Instructions.h"
#include "llvm/IR/IntrinsicInst.h"
#include "llvm/IR/Type.h"
#include "llvm/IR/Value.h"
#include "llvm/IR/InstIterator.h"
#include "llvm/Support/raw_ostream.h"
#include "llvm/IR/InlineAsm.h"
#include "ActivityAnalysis.h"
#include "Utils.h"
#if LLVM_VERSION_MAJOR >= 9
#include "llvm/Demangle/Demangle.h"
#endif
#include "FunctionUtils.h"
#include "LibraryFuncs.h"
#include "TypeAnalysis/TBAA.h"
#include "llvm/Analysis/ValueTracking.h"
using namespace llvm;
extern "C" {
cl::opt<bool>
EnzymePrintActivity("enzyme-print-activity", cl::init(false), cl::Hidden,
cl::desc("Print activity analysis algorithm"));
cl::opt<bool> EnzymeNonmarkedGlobalsInactive(
"enzyme-globals-default-inactive", cl::init(false), cl::Hidden,
cl::desc("Consider all nonmarked globals to be inactive"));
cl::opt<bool>
EnzymeEmptyFnInactive("enzyme-emptyfn-inactive", cl::init(false),
cl::Hidden,
cl::desc("Empty functions are considered inactive"));
cl::opt<bool>
EnzymeGlobalActivity("enzyme-global-activity", cl::init(false), cl::Hidden,
cl::desc("Enable correct global activity analysis"));
}
#include "llvm/IR/InstIterator.h"
#include <map>
#include <set>
#include <unordered_map>
const char *KnownInactiveFunctionsStartingWith[] = {
"f90io",
"$ss5print",
"_ZTv0_n24_NSoD", //"1Ev, 0Ev
"_ZNSt16allocator_traitsISaIdEE10deallocate",
"_ZNSaIcED1Ev",
"_ZNSaIcEC1Ev",
#if LLVM_VERSION_MAJOR <= 8
// TODO this returns allocated memory and thus can be an active value
// "_ZNSt16allocator_traits",
"_ZN4core3fmt",
"_ZN3std2io5stdio6_print",
"_ZNSt7__cxx1112basic_string",
"_ZNSt7__cxx1118basic_string",
"_ZNKSt7__cxx1112basic_string",
"_ZN9__gnu_cxx12__to_xstringINSt7__cxx1112basic_string",
"_ZNSt12__basic_file",
"_ZNSt15basic_streambufIcSt11char_traits",
"_ZNSt13basic_filebufIcSt11char_traits",
"_ZNSt14basic_ofstreamIcSt11char_traits",
"_ZNSi4readEPcl",
"_ZNKSt14basic_ifstreamIcSt11char_traits",
"_ZNSt14basic_ifstreamIcSt11char_traits",
"_ZNSo5writeEPKcl",
"_ZNSt19basic_ostringstreamIcSt11char_traits",
"_ZStrsIcSt11char_traitsIcESaIcEERSt13basic_istream",
"_ZStlsIcSt11char_traitsIcESaIcEERSt13basic_ostream",
"_ZNSt7__cxx1119basic_ostringstreamIcSt11char_traits",
"_ZNKSt7__cxx1119basic_ostringstreamIcSt11char_traits",
"_ZNSoD1Ev",
"_ZNSoC1EPSt15basic_streambufIcSt11char_traits",
"_ZStlsISt11char_traitsIcEERSt13basic_ostream",
"_ZSt16__ostream_insert",
"_ZStlsIwSt11char_traitsIwEERSt13basic_ostream",
"_ZNSo9_M_insert",
"_ZNSt13basic_ostream",
"_ZNSo3put",
"_ZNKSt5ctypeIcE13_M_widen_init",
"_ZNSi3get",
"_ZNSi7getline",
"_ZNSirsER",
"_ZNSt7__cxx1115basic_stringbuf",
"_ZNKSt7__cxx1115basic_stringbuf",
"_ZNSi6ignore",
"_ZNSt8ios_base",
"_ZNKSt9basic_ios",
"_ZNSt9basic_ios",
"_ZStorSt13_Ios_OpenmodeS_",
"_ZNSt6locale",
"_ZNKSt6locale4name",
"_ZStL8__ioinit"
"_ZNSt9basic_ios",
"_ZSt4cout",
"_ZSt3cin",
"_ZNSi10_M_extract",
"_ZNSolsE",
"_ZSt5flush",
"_ZNSo5flush",
"_ZSt4endl",
"_ZNSaIcE",
#endif
};
const char *KnownInactiveFunctionsContains[] = {
"__enzyme_float", "__enzyme_double", "__enzyme_integer",
"__enzyme_pointer"};
const std::set<std::string> InactiveGlobals = {
"ompi_request_null", "ompi_mpi_double", "ompi_mpi_comm_world", "stderr",
"stdout", "stdin", "_ZSt3cin", "_ZSt4cout", "_ZSt5wcout", "_ZSt4cerr",
"_ZTVNSt7__cxx1115basic_stringbufIcSt11char_traitsIcESaIcEEE",
"_ZTVSt15basic_streambufIcSt11char_traitsIcEE",
"_ZTVSt9basic_iosIcSt11char_traitsIcEE",
// istream
"_ZTVNSt7__cxx1119basic_istringstreamIcSt11char_traitsIcESaIcEEE",
"_ZTTNSt7__cxx1119basic_istringstreamIcSt11char_traitsIcESaIcEEE",
// ostream
"_ZTVNSt7__cxx1119basic_ostringstreamIcSt11char_traitsIcESaIcEEE",
"_ZTTNSt7__cxx1119basic_ostringstreamIcSt11char_traitsIcESaIcEEE",
// stringstream
"_ZTVNSt7__cxx1118basic_stringstreamIcSt11char_traitsIcESaIcEEE",
"_ZTTNSt7__cxx1118basic_stringstreamIcSt11char_traitsIcESaIcEEE",
// ifstream
"_ZTTSt14basic_ifstreamIcSt11char_traitsIcEE",
// ofstream
"_ZTTSt14basic_ofstreamIcSt11char_traitsIcEE",
// vtable for __cxxabiv1::__si_class_type_info
"_ZTVN10__cxxabiv120__si_class_type_infoE",
"_ZTVN10__cxxabiv117__class_type_infoE",
"_ZTVN10__cxxabiv121__vmi_class_type_infoE"};
const std::map<std::string, size_t> MPIInactiveCommAllocators = {
{"MPI_Graph_create", 5},
{"MPI_Comm_split", 2},
{"MPI_Intercomm_create", 6},
{"MPI_Comm_spawn", 6},
{"MPI_Comm_spawn_multiple", 7},
{"MPI_Comm_accept", 4},
{"MPI_Comm_connect", 4},
{"MPI_Comm_create", 2},
{"MPI_Comm_create_group", 3},
{"MPI_Comm_dup", 1},
{"MPI_Comm_dup", 2},
{"MPI_Comm_idup", 1},
{"MPI_Comm_join", 1},
};
// Instructions which themselves are inactive
// the returned value, however, may still be active
const std::set<std::string> KnownInactiveFunctionInsts = {
"__dynamic_cast",
"_ZSt18_Rb_tree_decrementPKSt18_Rb_tree_node_base",
"_ZSt18_Rb_tree_incrementPKSt18_Rb_tree_node_base",
"_ZSt18_Rb_tree_decrementPSt18_Rb_tree_node_base",
"_ZSt18_Rb_tree_incrementPSt18_Rb_tree_node_base",
"jl_ptr_to_array",
"jl_ptr_to_array_1d"};
const std::set<std::string> KnownInactiveFunctions = {
"abort",
"time",
"memcmp",
"gettimeofday",
"stat",
"mkdir",
"compress2",
"__assert_fail",
"__cxa_atexit",
"__cxa_guard_acquire",
"__cxa_guard_release",
"__cxa_guard_abort",
"snprintf",
"sprintf",
"printf",
"fprintf",
"putchar",
"fprintf",
"vprintf",
"vsnprintf",
"puts",
"fflush",
"__kmpc_for_static_init_4",
"__kmpc_for_static_init_4u",
"__kmpc_for_static_init_8",
"__kmpc_for_static_init_8u",
"__kmpc_for_static_fini",
"__kmpc_dispatch_init_4",
"__kmpc_dispatch_init_4u",
"__kmpc_dispatch_init_8",
"__kmpc_dispatch_init_8u",
"__kmpc_dispatch_next_4",
"__kmpc_dispatch_next_4u",
"__kmpc_dispatch_next_8",
"__kmpc_dispatch_next_8u",
"__kmpc_dispatch_fini_4",
"__kmpc_dispatch_fini_4u",
"__kmpc_dispatch_fini_8",
"__kmpc_dispatch_fini_8u",
"__kmpc_barrier",
"__kmpc_barrier_master",
"__kmpc_barrier_master_nowait",
"__kmpc_barrier_end_barrier_master",
"__kmpc_global_thread_num",
"omp_get_max_threads",
"malloc_usable_size",
"malloc_size",
"MPI_Init",
"MPI_Comm_size",
"PMPI_Comm_size",
"MPI_Comm_rank",
"PMPI_Comm_rank",
"MPI_Get_processor_name",
"MPI_Finalize",
"MPI_Test",
"MPI_Probe", // double check potential syncronization
"MPI_Barrier",
"MPI_Abort",
"MPI_Get_count",
"MPI_Comm_free",
"MPI_Comm_get_parent",
"MPI_Comm_get_name",
"MPI_Comm_get_info",
"MPI_Comm_remote_size",
"MPI_Comm_set_info",
"MPI_Comm_set_name",
"MPI_Comm_compare",
"MPI_Comm_call_errhandler",
"MPI_Comm_create_errhandler",
"MPI_Comm_disconnect",
"MPI_Wtime",
"_msize",
"ftnio_fmt_write64",
"f90_strcmp_klen",
"__swift_instantiateConcreteTypeFromMangledName",
"logb",
"logbf",
"logbl",
};
const char *DemangledKnownInactiveFunctionsStartingWith[] = {
// TODO this returns allocated memory and thus can be an active value
// "std::allocator",
"std::string",
"std::cerr",
"std::istream",
"std::ostream",
"std::ios_base",
"std::locale",
"std::ctype<char>",
"std::__basic_file",
"std::__ioinit",
"std::__basic_file",
// __cxx11
"std::__cxx11::basic_string",
"std::__cxx11::basic_ios",
"std::__cxx11::basic_ostringstream",
"std::__cxx11::basic_istringstream",
"std::__cxx11::basic_istream",
"std::__cxx11::basic_ostream",
"std::__cxx11::basic_ifstream",
"std::__cxx11::basic_ofstream",
"std::__cxx11::basic_stringbuf",
"std::__cxx11::basic_filebuf",
"std::__cxx11::basic_streambuf",
// non __cxx11
"std::basic_string",
"std::basic_ios",
"std::basic_ostringstream",
"std::basic_istringstream",
"std::basic_istream",
"std::basic_ostream",
"std::basic_ifstream",
"std::basic_ofstream",
"std::basic_stringbuf",
"std::basic_filebuf",
"std::basic_streambuf",
};
/// Is the use of value val as an argument of call CI known to be inactive
/// This tool can only be used when in DOWN mode
bool ActivityAnalyzer::isFunctionArgumentConstant(CallInst *CI, Value *val) {
assert(directions & DOWN);
if (CI->hasFnAttr("enzyme_inactive"))
return true;
Function *F = getFunctionFromCall(CI);
// Indirect function calls may actively use the argument
if (F == nullptr)
return false;
if (F->hasFnAttribute("enzyme_inactive")) {
return true;
}
auto Name = F->getName();
// Allocations, deallocations, and c++ guards don't impact the activity
// of arguments
if (isAllocationFunction(Name, TLI) || isDeallocationFunction(Name, TLI))
return true;
if (Name == "posix_memalign")
return true;
#if LLVM_VERSION_MAJOR >= 9
std::string demangledName = llvm::demangle(Name.str());
auto dName = StringRef(demangledName);
for (auto FuncName : DemangledKnownInactiveFunctionsStartingWith) {
if (dName.startswith(FuncName)) {
return true;
}
}
if (demangledName == Name.str()) {
// Either demangeling failed
// or they are equal but matching failed
// if (!Name.startswith("llvm."))
// llvm::errs() << "matching failed: " << Name.str() << " "
// << demangledName << "\n";
}
#endif
for (auto FuncName : KnownInactiveFunctionsStartingWith) {
if (Name.startswith(FuncName)) {
return true;
}
}
for (auto FuncName : KnownInactiveFunctionsContains) {
if (Name.contains(FuncName)) {
return true;
}
}
if (KnownInactiveFunctions.count(Name.str())) {
return true;
}
if (MPIInactiveCommAllocators.find(Name.str()) !=
MPIInactiveCommAllocators.end()) {
return true;
}
switch (F->getIntrinsicID()) {
case Intrinsic::nvvm_barrier0:
case Intrinsic::nvvm_barrier0_popc:
case Intrinsic::nvvm_barrier0_and:
case Intrinsic::nvvm_barrier0_or:
case Intrinsic::nvvm_membar_cta:
case Intrinsic::nvvm_membar_gl:
case Intrinsic::nvvm_membar_sys:
case Intrinsic::amdgcn_s_barrier:
case Intrinsic::assume:
case Intrinsic::stacksave:
case Intrinsic::stackrestore:
case Intrinsic::lifetime_start:
case Intrinsic::lifetime_end:
case Intrinsic::dbg_addr:
case Intrinsic::dbg_declare:
case Intrinsic::dbg_value:
case Intrinsic::invariant_start:
case Intrinsic::invariant_end:
case Intrinsic::var_annotation:
case Intrinsic::ptr_annotation:
case Intrinsic::annotation:
case Intrinsic::codeview_annotation:
case Intrinsic::expect:
case Intrinsic::type_test:
case Intrinsic::donothing:
case Intrinsic::prefetch:
case Intrinsic::trap:
#if LLVM_VERSION_MAJOR >= 8
case Intrinsic::is_constant:
#endif
case Intrinsic::memset:
return true;
default:
break;
}
/// Only the first argument (magnitude) of copysign is active
if (F->getIntrinsicID() == Intrinsic::copysign &&
CI->getArgOperand(0) != val) {
return true;
}
if (F->getIntrinsicID() == Intrinsic::memcpy && CI->getArgOperand(0) != val &&
CI->getArgOperand(1) != val)
return true;
if (F->getIntrinsicID() == Intrinsic::memmove &&
CI->getArgOperand(0) != val && CI->getArgOperand(1) != val)
return true;
// only the float arg input is potentially active
if (Name == "frexp" || Name == "frexpf" || Name == "frexpl") {
return val != CI->getOperand(0);
}
// The relerr argument is inactive
if (Name == "Faddeeva_erf" || Name == "Faddeeva_erfc" ||
Name == "Faddeeva_erfcx" || Name == "Faddeeva_erfi" ||
Name == "Faddeeva_dawson") {
#if LLVM_VERSION_MAJOR >= 14
for (size_t i = 0; i < CI->arg_size() - 1; i++)
#else
for (size_t i = 0; i < CI->getNumArgOperands() - 1; i++)
#endif
{
if (val == CI->getOperand(i))
return false;
}
return true;
}
// only the buffer is active for mpi send/recv
if (Name == "MPI_Recv" || Name == "PMPI_Recv" || Name == "MPI_Send" ||
Name == "PMPI_Send") {
return val != CI->getOperand(0);
}
// only the recv buffer and request is active for mpi isend/irecv
if (Name == "MPI_Irecv" || Name == "MPI_Isend") {
return val != CI->getOperand(0) && val != CI->getOperand(6);
}
// only request is active
if (Name == "MPI_Wait" || Name == "PMPI_Wait")
return val != CI->getOperand(0);
if (Name == "MPI_Waitall" || Name == "PMPI_Waitall")
return val != CI->getOperand(1);
// TODO interprocedural detection
// Before potential introprocedural detection, any function without definition
// may to be assumed to have an active use
if (F->empty())
return false;
// With all other options exhausted we have to assume this function could
// actively use the value
return false;
}
/// Call the function propagateFromOperand on all operands of CI
/// that could impact the activity of the call instruction
static inline void propagateArgumentInformation(
TargetLibraryInfo &TLI, CallInst &CI,
std::function<bool(Value *)> propagateFromOperand) {
if (auto F = CI.getCalledFunction()) {
// These functions are known to only have the first argument impact
// the activity of the call instruction
auto Name = F->getName();
if (Name == "lgamma" || Name == "lgammaf" || Name == "lgammal" ||
Name == "lgamma_r" || Name == "lgammaf_r" || Name == "lgammal_r" ||
Name == "__lgamma_r_finite" || Name == "__lgammaf_r_finite" ||
Name == "__lgammal_r_finite") {
propagateFromOperand(CI.getArgOperand(0));
return;
}
// Allocations, deallocations, and c++ guards are fully inactive
if (isAllocationFunction(Name, TLI) || isDeallocationFunction(Name, TLI) ||
Name == "__cxa_guard_acquire" || Name == "__cxa_guard_release" ||
Name == "__cxa_guard_abort")
return;
/// Only the first argument (magnitude) of copysign is active
if (F->getIntrinsicID() == Intrinsic::copysign) {
propagateFromOperand(CI.getOperand(0));
return;
}
// Certain intrinsics are inactive by definition
// and have nothing to propagate.
switch (F->getIntrinsicID()) {
case Intrinsic::nvvm_barrier0:
case Intrinsic::nvvm_barrier0_popc:
case Intrinsic::nvvm_barrier0_and:
case Intrinsic::nvvm_barrier0_or:
case Intrinsic::nvvm_membar_cta:
case Intrinsic::nvvm_membar_gl:
case Intrinsic::nvvm_membar_sys:
case Intrinsic::amdgcn_s_barrier:
case Intrinsic::assume:
case Intrinsic::stacksave:
case Intrinsic::stackrestore:
case Intrinsic::lifetime_start:
case Intrinsic::lifetime_end:
case Intrinsic::dbg_addr:
case Intrinsic::dbg_declare:
case Intrinsic::dbg_value:
case Intrinsic::invariant_start:
case Intrinsic::invariant_end:
case Intrinsic::var_annotation:
case Intrinsic::ptr_annotation:
case Intrinsic::annotation:
case Intrinsic::codeview_annotation:
case Intrinsic::expect:
case Intrinsic::type_test:
case Intrinsic::donothing:
case Intrinsic::prefetch:
case Intrinsic::trap:
#if LLVM_VERSION_MAJOR >= 8
case Intrinsic::is_constant:
#endif
case Intrinsic::memset:
return;
default:
break;
}
if (F->getIntrinsicID() == Intrinsic::memcpy ||
F->getIntrinsicID() == Intrinsic::memmove) {
propagateFromOperand(CI.getOperand(0));
propagateFromOperand(CI.getOperand(1));
return;
}
if (Name == "frexp" || Name == "frexpf" || Name == "frexpl") {
propagateFromOperand(CI.getOperand(0));
return;
}
if (Name == "Faddeeva_erf" || Name == "Faddeeva_erfc" ||
Name == "Faddeeva_erfcx" || Name == "Faddeeva_erfi" ||
Name == "Faddeeva_dawson") {
#if LLVM_VERSION_MAJOR >= 14
for (size_t i = 0; i < CI.arg_size() - 1; i++)
#else
for (size_t i = 0; i < CI.getNumArgOperands() - 1; i++)
#endif
{
propagateFromOperand(CI.getOperand(i));
}
return;
}
if (Name == "julia.call" || Name == "julia.call2") {
#if LLVM_VERSION_MAJOR >= 14
for (size_t i = 1; i < CI.arg_size(); i++)
#else
for (size_t i = 1; i < CI.getNumArgOperands(); i++)
#endif
{
propagateFromOperand(CI.getOperand(i));
}
return;
}
}
// For other calls, check all operands of the instruction
// as conservatively they may impact the activity of the call
#if LLVM_VERSION_MAJOR >= 14
for (auto &a : CI.args())
#else
for (auto &a : CI.arg_operands())
#endif
{
if (propagateFromOperand(a))
break;
}
}
/// Return whether this instruction is known not to propagate adjoints
/// Note that instructions could return an active pointer, but
/// do not propagate adjoints themselves
bool ActivityAnalyzer::isConstantInstruction(TypeResults const &TR,
Instruction *I) {
// This analysis may only be called by instructions corresponding to
// the function analyzed by TypeInfo
assert(I);
assert(TR.getFunction() == I->getParent()->getParent());
// The return instruction doesn't impact activity (handled specifically
// during adjoint generation)
if (isa<ReturnInst>(I))
return true;
// Branch, unreachable, and previously computed constants are inactive
if (isa<UnreachableInst>(I) || isa<BranchInst>(I) ||
(ConstantInstructions.find(I) != ConstantInstructions.end())) {
return true;
}
/// Previously computed inactives remain inactive
if ((ActiveInstructions.find(I) != ActiveInstructions.end())) {
return false;
}
if (notForAnalysis.count(I->getParent())) {
if (EnzymePrintActivity)
llvm::errs() << " constant instruction as dominates unreachable " << *I
<< "\n";
InsertConstantInstruction(TR, I);
return true;
}
if (isa<FenceInst>(I)) {
if (EnzymePrintActivity)
llvm::errs() << " constant fence instruction " << *I << "\n";
InsertConstantInstruction(TR, I);
return true;
}
if (auto CI = dyn_cast<CallInst>(I)) {
if (CI->hasFnAttr("enzyme_active")) {
if (EnzymePrintActivity)
llvm::errs() << "forced active " << *I << "\n";
ActiveInstructions.insert(I);
return false;
}
if (CI->hasFnAttr("enzyme_inactive")) {
if (EnzymePrintActivity)
llvm::errs() << "forced inactive " << *I << "\n";
InsertConstantInstruction(TR, I);
return true;
}
Function *called = getFunctionFromCall(CI);
if (called) {
if (called->hasFnAttribute("enzyme_active")) {
if (EnzymePrintActivity)
llvm::errs() << "forced active " << *I << "\n";
ActiveInstructions.insert(I);
return false;
}
if (called->hasFnAttribute("enzyme_inactive")) {
if (EnzymePrintActivity)
llvm::errs() << "forced inactive " << *I << "\n";
InsertConstantInstruction(TR, I);
return true;
}
if (KnownInactiveFunctionInsts.count(called->getName().str())) {
InsertConstantInstruction(TR, I);
return true;
}
}
}
/// A store into all integral memory is inactive
if (auto SI = dyn_cast<StoreInst>(I)) {
auto StoreSize = SI->getParent()
->getParent()
->getParent()
->getDataLayout()
.getTypeSizeInBits(SI->getValueOperand()->getType()) /
8;
bool AllIntegral = true;
bool SeenInteger = false;
auto q = TR.query(SI->getPointerOperand()).Data0();
for (int i = -1; i < (int)StoreSize; ++i) {
auto dt = q[{i}];
if (dt.isIntegral() || dt == BaseType::Anything) {
SeenInteger = true;
if (i == -1)
break;
} else if (dt.isKnown()) {
AllIntegral = false;
break;
}
}
if (AllIntegral && SeenInteger) {
if (EnzymePrintActivity)
llvm::errs() << " constant instruction from TA " << *I << "\n";
InsertConstantInstruction(TR, I);
return true;
}
}
if (auto SI = dyn_cast<AtomicRMWInst>(I)) {
auto StoreSize = SI->getParent()
->getParent()
->getParent()
->getDataLayout()
.getTypeSizeInBits(I->getType()) /
8;
bool AllIntegral = true;
bool SeenInteger = false;
auto q = TR.query(SI->getOperand(0)).Data0();
for (int i = -1; i < (int)StoreSize; ++i) {
auto dt = q[{i}];
if (dt.isIntegral() || dt == BaseType::Anything) {
SeenInteger = true;
if (i == -1)
break;
} else if (dt.isKnown()) {
AllIntegral = false;
break;
}
}
if (AllIntegral && SeenInteger) {
if (EnzymePrintActivity)
llvm::errs() << " constant instruction from TA " << *I << "\n";
InsertConstantInstruction(TR, I);
return true;
}
}
if (EnzymePrintActivity)
llvm::errs() << "checking if is constant[" << (int)directions << "] " << *I
<< "\n";
if (auto II = dyn_cast<IntrinsicInst>(I)) {
switch (II->getIntrinsicID()) {
case Intrinsic::nvvm_barrier0:
case Intrinsic::nvvm_barrier0_popc:
case Intrinsic::nvvm_barrier0_and:
case Intrinsic::nvvm_barrier0_or:
case Intrinsic::nvvm_membar_cta:
case Intrinsic::nvvm_membar_gl:
case Intrinsic::nvvm_membar_sys:
case Intrinsic::amdgcn_s_barrier:
case Intrinsic::assume:
case Intrinsic::stacksave:
case Intrinsic::stackrestore:
case Intrinsic::lifetime_start:
case Intrinsic::lifetime_end:
case Intrinsic::dbg_addr:
case Intrinsic::dbg_declare:
case Intrinsic::dbg_value:
case Intrinsic::invariant_start:
case Intrinsic::invariant_end:
case Intrinsic::var_annotation:
case Intrinsic::ptr_annotation:
case Intrinsic::annotation:
case Intrinsic::codeview_annotation:
case Intrinsic::expect:
case Intrinsic::type_test:
case Intrinsic::donothing:
case Intrinsic::prefetch:
case Intrinsic::trap:
#if LLVM_VERSION_MAJOR >= 8
case Intrinsic::is_constant:
#endif
case Intrinsic::memset:
if (EnzymePrintActivity)
llvm::errs() << "known inactive intrinsic " << *I << "\n";
InsertConstantInstruction(TR, I);
return true;
default:
break;
}
}
// Analyzer for inductive assumption where we attempt to prove this is
// inactive from a lack of active users
std::shared_ptr<ActivityAnalyzer> DownHypothesis;
// If this instruction does not write to memory that outlives itself
// (potentially propagating derivative information), the only way to propagate
// derivative information is through the return value
// TODO the "doesn't write to active memory" can be made more aggressive than
// doesn't write to any memory
bool noActiveWrite = false;
if (!I->mayWriteToMemory())
noActiveWrite = true;
else if (auto CI = dyn_cast<CallInst>(I)) {
if (AA.onlyReadsMemory(CI)) {
noActiveWrite = true;
} else if (auto F = CI->getCalledFunction()) {
if (isMemFreeLibMFunction(F->getName())) {
noActiveWrite = true;
} else if (F->getName() == "frexp" || F->getName() == "frexpf" ||
F->getName() == "frexpl") {
noActiveWrite = true;
}
}
}
if (noActiveWrite) {
// Even if returning a pointer, this instruction is considered inactive
// since the instruction doesn't prop gradients. Thus, so long as we don't
// return an object containing a float, this instruction is inactive
if (!TR.intType(1, I, /*errifNotFound*/ false).isPossibleFloat()) {
if (EnzymePrintActivity)
llvm::errs()
<< " constant instruction from known non-float non-writing "
"instruction "
<< *I << "\n";
InsertConstantInstruction(TR, I);
return true;
}
// If the value returned is constant otherwise, the instruction is inactive
if (isConstantValue(TR, I)) {
if (EnzymePrintActivity)
llvm::errs() << " constant instruction from known constant non-writing "
"instruction "
<< *I << "\n";
InsertConstantInstruction(TR, I);
return true;
}
// Even if the return is nonconstant, it's worth checking explicitly the
// users since unlike isConstantValue, returning a pointer does not make the
// instruction active
if (directions & DOWN) {
// We shall now induct on this instruction being inactive and try to prove
// this fact from a lack of active users.
// If we aren't a phi node (and thus potentially recursive on uses) and
// already equal to the current direction, we don't need to induct,
// reducing runtime.
if (directions == DOWN && !isa<PHINode>(I)) {
if (isValueInactiveFromUsers(TR, I, UseActivity::None)) {
if (EnzymePrintActivity)
llvm::errs() << " constant instruction[" << (int)directions
<< "] from users instruction " << *I << "\n";
InsertConstantInstruction(TR, I);
return true;
}
} else {
DownHypothesis = std::shared_ptr<ActivityAnalyzer>(
new ActivityAnalyzer(*this, DOWN));
DownHypothesis->ConstantInstructions.insert(I);
if (DownHypothesis->isValueInactiveFromUsers(TR, I,
UseActivity::None)) {
if (EnzymePrintActivity)
llvm::errs() << " constant instruction[" << (int)directions
<< "] from users instruction " << *I << "\n";
InsertConstantInstruction(TR, I);
insertConstantsFrom(TR, *DownHypothesis);
return true;
}
}
}
}
std::shared_ptr<ActivityAnalyzer> UpHypothesis;
if (directions & UP) {
// If this instruction has no active operands, the instruction
// is active.
// TODO This isn't 100% accurate and will incorrectly mark a no-argument
// function that reads from active memory as constant
// Technically the additional constraint is that this does not read from
// active memory, where we have assumed that the only active memory
// we care about is accessible from arguments passed (and thus not globals)
UpHypothesis =
std::shared_ptr<ActivityAnalyzer>(new ActivityAnalyzer(*this, UP));
UpHypothesis->ConstantInstructions.insert(I);
assert(directions & UP);
if (UpHypothesis->isInstructionInactiveFromOrigin(TR, I)) {
if (EnzymePrintActivity)
llvm::errs() << " constant instruction from origin "
"instruction "
<< *I << "\n";
InsertConstantInstruction(TR, I);
insertConstantsFrom(TR, *UpHypothesis);
if (DownHypothesis)
insertConstantsFrom(TR, *DownHypothesis);
return true;
} else if (directions == 3) {
if (isa<LoadInst>(I) || isa<StoreInst>(I) || isa<BinaryOperator>(I)) {
for (auto &op : I->operands()) {
if (!UpHypothesis->isConstantValue(TR, op)) {
ReEvaluateInstIfInactiveValue[op].insert(I);
}
}
}
}
}
// Otherwise we must fall back and assume this instruction to be active.
ActiveInstructions.insert(I);
if (EnzymePrintActivity)
llvm::errs() << "couldnt decide fallback as nonconstant instruction("
<< (int)directions << "):" << *I << "\n";
if (noActiveWrite && directions == 3)
ReEvaluateInstIfInactiveValue[I].insert(I);
return false;
}
bool isValuePotentiallyUsedAsPointer(llvm::Value *val) {
std::deque<llvm::Value *> todo = {val};
SmallPtrSet<Value *, 3> seen;
while (todo.size()) {
auto cur = todo.back();
todo.pop_back();
if (seen.count(cur))
continue;
seen.insert(cur);
for (auto u : cur->users()) {
if (isa<ReturnInst>(u))
return true;
if (!cast<Instruction>(u)->mayReadOrWriteMemory()) {
todo.push_back(u);
continue;
}
if (EnzymePrintActivity)
llvm::errs() << " VALUE potentially used as pointer " << *val << " by "
<< *u << "\n";
return true;
}
}
return false;
}
bool ActivityAnalyzer::isConstantValue(TypeResults const &TR, Value *Val) {
// This analysis may only be called by instructions corresponding to
// the function analyzed by TypeInfo -- however if the Value
// was created outside a function (e.g. global, constant), that is allowed
assert(Val);
if (auto I = dyn_cast<Instruction>(Val)) {
if (TR.getFunction() != I->getParent()->getParent()) {
llvm::errs() << *TR.getFunction() << "\n";
llvm::errs() << *I << "\n";
}
assert(TR.getFunction() == I->getParent()->getParent());
}
if (auto Arg = dyn_cast<Argument>(Val)) {
assert(TR.getFunction() == Arg->getParent());
}
// Void values are definitionally inactive
if (Val->getType()->isVoidTy())
return true;
// Token values are definitionally inactive
if (Val->getType()->isTokenTy())
return true;
// All function pointers are considered active in case an augmented primal
// or reverse is needed
if (isa<Function>(Val) || isa<InlineAsm>(Val)) {
return false;
}
/// If we've already shown this value to be inactive
if (ConstantValues.find(Val) != ConstantValues.end()) {
return true;
}
/// If we've already shown this value to be active
if (ActiveValues.find(Val) != ActiveValues.end()) {
return false;
}
if (auto CD = dyn_cast<ConstantDataSequential>(Val)) {
// inductively assume inactive
ConstantValues.insert(CD);
for (size_t i = 0, len = CD->getNumElements(); i < len; i++) {
if (!isConstantValue(TR, CD->getElementAsConstant(i))) {
ConstantValues.erase(CD);
ActiveValues.insert(CD);
return false;
}
}
return true;
}
if (auto CD = dyn_cast<ConstantAggregate>(Val)) {
// inductively assume inactive
ConstantValues.insert(CD);
for (size_t i = 0, len = CD->getNumOperands(); i < len; i++) {
if (!isConstantValue(TR, CD->getOperand(i))) {
ConstantValues.erase(CD);
ActiveValues.insert(CD);
return false;
}
}
return true;
}
// Undef, metadata, non-global constants, and blocks are inactive
if (isa<UndefValue>(Val) || isa<MetadataAsValue>(Val) ||
isa<ConstantData>(Val) || isa<ConstantAggregate>(Val) ||
isa<BasicBlock>(Val)) {
return true;
}
if (auto II = dyn_cast<IntrinsicInst>(Val)) {
switch (II->getIntrinsicID()) {
case Intrinsic::nvvm_barrier0:
case Intrinsic::nvvm_barrier0_popc:
case Intrinsic::nvvm_barrier0_and:
case Intrinsic::nvvm_barrier0_or:
case Intrinsic::nvvm_membar_cta:
case Intrinsic::nvvm_membar_gl:
case Intrinsic::nvvm_membar_sys:
case Intrinsic::amdgcn_s_barrier:
case Intrinsic::assume:
case Intrinsic::stacksave:
case Intrinsic::stackrestore:
case Intrinsic::lifetime_start:
case Intrinsic::lifetime_end:
case Intrinsic::dbg_addr:
case Intrinsic::dbg_declare:
case Intrinsic::dbg_value:
case Intrinsic::invariant_start:
case Intrinsic::invariant_end:
case Intrinsic::var_annotation:
case Intrinsic::ptr_annotation:
case Intrinsic::annotation:
case Intrinsic::codeview_annotation:
case Intrinsic::expect:
case Intrinsic::type_test:
case Intrinsic::donothing:
case Intrinsic::prefetch:
#if LLVM_VERSION_MAJOR >= 8
case Intrinsic::is_constant:
#endif
InsertConstantValue(TR, Val);
return true;
default:
break;
}
}
// All arguments must be marked constant/nonconstant ahead of time
if (isa<Argument>(Val) && !cast<Argument>(Val)->hasByValAttr()) {
llvm::errs() << *(cast<Argument>(Val)->getParent()) << "\n";
llvm::errs() << *Val << "\n";
assert(0 && "must've put arguments in constant/nonconstant");
}
// This value is certainly an integer (and only and integer, not a pointer or
// float). Therefore its value is constant
if (TR.query(Val)[{-1}] == BaseType::Integer) {
if (EnzymePrintActivity)
llvm::errs() << " Value const as integral " << (int)directions << " "
<< *Val << " "
<< TR.intType(1, Val, /*errIfNotFound*/ false).str() << "\n";
InsertConstantValue(TR, Val);
return true;
}
#if 0
// This value is certainly a pointer to an integer (and only and integer, not
// a pointer or float). Therefore its value is constant
// TODO use typeInfo for more aggressive activity analysis
if (val->getType()->isPointerTy() &&
cast<PointerType>(val->getType())->isIntOrIntVectorTy() &&
TR.firstPointer(1, val, /*errifnotfound*/ false).isIntegral()) {
if (EnzymePrintActivity)
llvm::errs() << " Value const as integral pointer" << (int)directions
<< " " << *val << "\n";
InsertConstantValue(TR, Val);
return true;
}
#endif
if (auto GI = dyn_cast<GlobalVariable>(Val)) {
// If operating under the assumption globals are inactive unless
// explicitly marked as active, this is inactive
if (!hasMetadata(GI, "enzyme_shadow") && EnzymeNonmarkedGlobalsInactive) {
InsertConstantValue(TR, Val);
return true;
}
if (hasMetadata(GI, "enzyme_inactive")) {
InsertConstantValue(TR, Val);
return true;
}
if (GI->getName().contains("enzyme_const") ||
InactiveGlobals.count(GI->getName().str())) {
InsertConstantValue(TR, Val);
return true;
}
// If this global is unchanging and the internal constant data
// is inactive, the global is inactive
if (GI->isConstant() && GI->hasInitializer() &&
isConstantValue(TR, GI->getInitializer())) {
InsertConstantValue(TR, Val);
if (EnzymePrintActivity)
llvm::errs() << " VALUE const global " << *Val
<< " init: " << *GI->getInitializer() << "\n";
return true;
}
// If this global is a pointer to an integer, it is inactive
// TODO note this may need updating to consider the size
// of the global
auto res = TR.query(GI).Data0();
auto dt = res[{-1}];
if (dt.isIntegral()) {
if (EnzymePrintActivity)
llvm::errs() << " VALUE const as global int pointer " << *Val
<< " type - " << res.str() << "\n";
InsertConstantValue(TR, Val);
return true;
}
// If this is a global local to this translation unit with inactive
// initializer and no active uses, it is definitionally inactive
bool usedJustInThisModule =
GI->hasInternalLinkage() || GI->hasPrivateLinkage();
if (EnzymePrintActivity)
llvm::errs() << "pre attempting(" << (int)directions
<< ") just used in module for: " << *GI << " dir"
<< (int)directions << " justusedin:" << usedJustInThisModule
<< "\n";
if (directions == 3 && usedJustInThisModule) {
// TODO this assumes global initializer cannot refer to itself (lest
// infinite loop)
if (!GI->hasInitializer() || isConstantValue(TR, GI->getInitializer())) {
if (EnzymePrintActivity)
llvm::errs() << "attempting just used in module for: " << *GI << "\n";
// Not looking at users to prove inactive (definition of down)
// If all users are inactive, this is therefore inactive.
// Since we won't look at origins to prove, we can inductively assume
// this is inactive
// As an optimization if we are going down already
// and we won't use ourselves (done by PHI's), we
// dont need to inductively assume we're true
// and can instead use this object!
// This pointer is inactive if it is either not actively stored to or
// not actively loaded from
// See alloca logic to explain why OnlyStores is insufficient here
if (directions == DOWN) {
if (isValueInactiveFromUsers(TR, Val, UseActivity::OnlyLoads)) {
InsertConstantValue(TR, Val);
return true;
}
} else {
Instruction *LoadReval = nullptr;
Instruction *StoreReval = nullptr;
auto DownHypothesis = std::shared_ptr<ActivityAnalyzer>(
new ActivityAnalyzer(*this, DOWN));
DownHypothesis->ConstantValues.insert(Val);
if (DownHypothesis->isValueInactiveFromUsers(
TR, Val, UseActivity::OnlyLoads, &LoadReval) ||
(TR.query(GI)[{-1, -1}].isFloat() &&
DownHypothesis->isValueInactiveFromUsers(
TR, Val, UseActivity::OnlyStores, &StoreReval))) {
insertConstantsFrom(TR, *DownHypothesis);
InsertConstantValue(TR, Val);
return true;
} else {
if (LoadReval) {
if (EnzymePrintActivity)
llvm::errs() << " global activity of " << *Val
<< " dependant on " << *LoadReval << "\n";
ReEvaluateValueIfInactiveInst[LoadReval].insert(Val);
}
if (StoreReval)
ReEvaluateValueIfInactiveInst[StoreReval].insert(Val);
}
}
}
}
// Otherwise we have to assume this global is active since it can
// be arbitrarily used in an active way
// TODO we can be more aggressive here in the future
if (EnzymePrintActivity)
llvm::errs() << " VALUE nonconst unknown global " << *Val << " type - "
<< res.str() << "\n";
ActiveValues.insert(Val);
return false;
}
// ConstantExpr's are inactive if their arguments are inactive
// Note that since there can't be a recursive constant this shouldn't
// infinite loop
if (auto ce = dyn_cast<ConstantExpr>(Val)) {
if (ce->isCast()) {
if (isConstantValue(TR, ce->getOperand(0))) {
if (EnzymePrintActivity)
llvm::errs() << " VALUE const cast from from operand " << *Val
<< "\n";
InsertConstantValue(TR, Val);
return true;
}
}
if (ce->getOpcode() == Instruction::GetElementPtr &&
llvm::all_of(ce->operand_values(),
[&](Value *v) { return isConstantValue(TR, v); })) {
if (isConstantValue(TR, ce->getOperand(0))) {
if (EnzymePrintActivity)
llvm::errs() << " VALUE const cast from gep operand " << *Val << "\n";
InsertConstantValue(TR, Val);
return true;
}
}
if (EnzymePrintActivity)
llvm::errs() << " VALUE nonconst unknown expr " << *Val << "\n";
ActiveValues.insert(Val);
return false;
}
if (auto CI = dyn_cast<CallInst>(Val)) {
if (CI->hasFnAttr("enzyme_active")) {
if (EnzymePrintActivity)
llvm::errs() << "forced active val " << *Val << "\n";
ActiveValues.insert(Val);
return false;
}
if (CI->hasFnAttr("enzyme_inactive")) {
if (EnzymePrintActivity)
llvm::errs() << "forced inactive val " << *Val << "\n";
InsertConstantValue(TR, Val);
return true;
}
Function *called = getFunctionFromCall(CI);
if (called) {
if (called->hasFnAttribute("enzyme_active")) {
if (EnzymePrintActivity)
llvm::errs() << "forced active val " << *Val << "\n";
ActiveValues.insert(Val);
return false;
}
if (called->hasFnAttribute("enzyme_inactive")) {
if (EnzymePrintActivity)
llvm::errs() << "forced inactive val " << *Val << "\n";
InsertConstantValue(TR, Val);
return true;
}
}
}
std::shared_ptr<ActivityAnalyzer> UpHypothesis;
// Handle types that could contain pointers
// Consider all types except
// * floating point types (since those are assumed not pointers)
// * integers that we know are not pointers
bool containsPointer = true;
if (Val->getType()->isFPOrFPVectorTy())
containsPointer = false;
if (!TR.intType(1, Val, /*errIfNotFound*/ false).isPossiblePointer())
containsPointer = false;
if (containsPointer && !isValuePotentiallyUsedAsPointer(Val)) {
containsPointer = false;
}
if (containsPointer) {
auto TmpOrig =
#if LLVM_VERSION_MAJOR >= 12
getUnderlyingObject(Val, 100);
#else
GetUnderlyingObject(Val, TR.getFunction()->getParent()->getDataLayout(),
100);
#endif
// If we know that our origin is inactive from its arguments,
// we are definitionally inactive
if (directions & UP) {
// If we are derived from an argument our activity is equal to the
// activity of the argument by definition
if (auto arg = dyn_cast<Argument>(TmpOrig)) {
if (!arg->hasByValAttr()) {
bool res = isConstantValue(TR, TmpOrig);
if (res) {
if (EnzymePrintActivity)
llvm::errs() << " arg const from orig val=" << *Val
<< " orig=" << *TmpOrig << "\n";
InsertConstantValue(TR, Val);
} else {
if (EnzymePrintActivity)
llvm::errs() << " arg active from orig val=" << *Val
<< " orig=" << *TmpOrig << "\n";
ActiveValues.insert(Val);
}
return res;
}
}
UpHypothesis =
std::shared_ptr<ActivityAnalyzer>(new ActivityAnalyzer(*this, UP));
UpHypothesis->ConstantValues.insert(Val);
// If our origin is a load of a known inactive (say inactive argument), we
// are also inactive
if (auto PN = dyn_cast<PHINode>(TmpOrig)) {
// Not taking fast path incase phi is recursive.
Value *active = nullptr;
for (auto &V : PN->incoming_values()) {
if (!UpHypothesis->isConstantValue(TR, V.get())) {
active = V.get();
break;
}
}
if (!active) {
InsertConstantValue(TR, Val);
if (TmpOrig != Val) {
InsertConstantValue(TR, TmpOrig);
}
insertConstantsFrom(TR, *UpHypothesis);
return true;
} else {
ReEvaluateValueIfInactiveValue[active].insert(Val);
if (TmpOrig != Val) {
ReEvaluateValueIfInactiveValue[active].insert(TmpOrig);
}
}
} else if (auto LI = dyn_cast<LoadInst>(TmpOrig)) {
if (directions == UP) {
if (isConstantValue(TR, LI->getPointerOperand())) {
InsertConstantValue(TR, Val);
return true;
}
} else {
if (UpHypothesis->isConstantValue(TR, LI->getPointerOperand())) {
InsertConstantValue(TR, Val);
insertConstantsFrom(TR, *UpHypothesis);
return true;
}
}
} else if (isa<IntrinsicInst>(TmpOrig) &&
(cast<IntrinsicInst>(TmpOrig)->getIntrinsicID() ==
Intrinsic::nvvm_ldu_global_i ||
cast<IntrinsicInst>(TmpOrig)->getIntrinsicID() ==
Intrinsic::nvvm_ldu_global_p ||
cast<IntrinsicInst>(TmpOrig)->getIntrinsicID() ==
Intrinsic::nvvm_ldu_global_f ||
cast<IntrinsicInst>(TmpOrig)->getIntrinsicID() ==
Intrinsic::nvvm_ldg_global_i ||
cast<IntrinsicInst>(TmpOrig)->getIntrinsicID() ==
Intrinsic::nvvm_ldg_global_p ||
cast<IntrinsicInst>(TmpOrig)->getIntrinsicID() ==
Intrinsic::nvvm_ldg_global_f)) {
auto II = cast<IntrinsicInst>(TmpOrig);
if (directions == UP) {
if (isConstantValue(TR, II->getOperand(0))) {
InsertConstantValue(TR, Val);
return true;
}
} else {
if (UpHypothesis->isConstantValue(TR, II->getOperand(0))) {
InsertConstantValue(TR, Val);
insertConstantsFrom(TR, *UpHypothesis);
return true;
}
}
} else if (auto op = dyn_cast<CallInst>(TmpOrig)) {
if (op->hasFnAttr("enzyme_inactive")) {
InsertConstantValue(TR, Val);
insertConstantsFrom(TR, *UpHypothesis);
return true;
}
Function *called = getFunctionFromCall(op);
StringRef funcName = getFuncNameFromCall(op);
if (called && called->hasFnAttribute("enzyme_inactive")) {
InsertConstantValue(TR, Val);
insertConstantsFrom(TR, *UpHypothesis);
return true;
}
if (funcName == "free" || funcName == "_ZdlPv" ||
funcName == "_ZdlPvm" || funcName == "munmap") {
InsertConstantValue(TR, Val);
insertConstantsFrom(TR, *UpHypothesis);
return true;
}
#if LLVM_VERSION_MAJOR >= 9
auto dName = demangle(funcName.str());
for (auto FuncName : DemangledKnownInactiveFunctionsStartingWith) {
if (StringRef(dName).startswith(FuncName)) {
InsertConstantValue(TR, Val);
insertConstantsFrom(TR, *UpHypothesis);
return true;
}
}
#endif
for (auto FuncName : KnownInactiveFunctionsStartingWith) {
if (funcName.startswith(FuncName)) {
InsertConstantValue(TR, Val);
insertConstantsFrom(TR, *UpHypothesis);
return true;
}
}
for (auto FuncName : KnownInactiveFunctionsContains) {
if (funcName.contains(FuncName)) {
InsertConstantValue(TR, Val);
insertConstantsFrom(TR, *UpHypothesis);
return true;
}
}
if (KnownInactiveFunctions.count(funcName.str()) ||
MPIInactiveCommAllocators.find(funcName.str()) !=
MPIInactiveCommAllocators.end()) {
InsertConstantValue(TR, Val);
insertConstantsFrom(TR, *UpHypothesis);
return true;
}
if (called && called->getIntrinsicID() == Intrinsic::trap) {
InsertConstantValue(TR, Val);
insertConstantsFrom(TR, *UpHypothesis);
return true;
}
// If requesting empty unknown functions to be considered inactive,
// abide by those rules
if (called && EnzymeEmptyFnInactive && called->empty() &&
!hasMetadata(called, "enzyme_gradient") &&
!hasMetadata(called, "enzyme_derivative") &&
!isAllocationFunction(funcName, TLI) &&
!isDeallocationFunction(funcName, TLI) && !isa<IntrinsicInst>(op)) {
InsertConstantValue(TR, Val);
insertConstantsFrom(TR, *UpHypothesis);
return true;
}
if (isAllocationFunction(funcName, TLI)) {
// This pointer is inactive if it is either not actively stored to
// and not actively loaded from.
if (directions == DOWN) {
for (auto UA :
{UseActivity::OnlyLoads, UseActivity::OnlyNonPointerStores,
UseActivity::AllStores, UseActivity::None}) {
Instruction *LoadReval = nullptr;
if (isValueInactiveFromUsers(TR, TmpOrig, UA, &LoadReval)) {
InsertConstantValue(TR, Val);
return true;
}
if (LoadReval && UA != UseActivity::AllStores) {
ReEvaluateValueIfInactiveInst[LoadReval].insert(TmpOrig);
}
}
} else if (directions & DOWN) {
auto DownHypothesis = std::shared_ptr<ActivityAnalyzer>(
new ActivityAnalyzer(*this, DOWN));
DownHypothesis->ConstantValues.insert(TmpOrig);
for (auto UA :
{UseActivity::OnlyLoads, UseActivity::OnlyNonPointerStores,
UseActivity::AllStores, UseActivity::None}) {
Instruction *LoadReval = nullptr;
if (DownHypothesis->isValueInactiveFromUsers(TR, TmpOrig, UA,
&LoadReval)) {
insertConstantsFrom(TR, *DownHypothesis);
InsertConstantValue(TR, Val);
return true;
} else {
if (LoadReval && UA != UseActivity::AllStores) {
ReEvaluateValueIfInactiveInst[LoadReval].insert(TmpOrig);
}
}
}
}
}
if (funcName == "jl_array_copy" || funcName == "ijl_array_copy") {
// This pointer is inactive if it is either not actively stored to
// and not actively loaded from.
if (directions & DOWN && directions & UP) {
if (UpHypothesis->isConstantValue(TR, op->getOperand(0))) {
auto DownHypothesis = std::shared_ptr<ActivityAnalyzer>(
new ActivityAnalyzer(*this, DOWN));
DownHypothesis->ConstantValues.insert(TmpOrig);
for (auto UA :
{UseActivity::OnlyLoads, UseActivity::OnlyNonPointerStores,
UseActivity::AllStores, UseActivity::None}) {
Instruction *LoadReval = nullptr;
if (DownHypothesis->isValueInactiveFromUsers(TR, TmpOrig, UA,
&LoadReval)) {
insertConstantsFrom(TR, *DownHypothesis);
InsertConstantValue(TR, Val);
return true;
} else {
if (LoadReval && UA != UseActivity::AllStores) {
ReEvaluateValueIfInactiveInst[LoadReval].insert(TmpOrig);
}
}
}
}
}
}
} else if (isa<AllocaInst>(Val)) {
// This pointer is inactive if it is either not actively stored to or
// not actively loaded from and is nonescaping by definition of being
// alloca OnlyStores is insufficient here since the loaded pointer can
// have active memory stored into it [e.g. not just top level pointer
// that matters]
if (directions == DOWN) {
for (auto UA :
{UseActivity::OnlyLoads, UseActivity::OnlyNonPointerStores,
UseActivity::AllStores, UseActivity::None}) {
Instruction *LoadReval = nullptr;
if (isValueInactiveFromUsers(TR, TmpOrig, UA, &LoadReval)) {
InsertConstantValue(TR, Val);
return true;
}
if (LoadReval && UA != UseActivity::AllStores) {
ReEvaluateValueIfInactiveInst[LoadReval].insert(TmpOrig);
}
}
} else if (directions & DOWN) {
auto DownHypothesis = std::shared_ptr<ActivityAnalyzer>(
new ActivityAnalyzer(*this, DOWN));
DownHypothesis->ConstantValues.insert(TmpOrig);
for (auto UA :
{UseActivity::OnlyLoads, UseActivity::OnlyNonPointerStores,
UseActivity::AllStores, UseActivity::None}) {
Instruction *LoadReval = nullptr;
if (DownHypothesis->isValueInactiveFromUsers(TR, TmpOrig, UA,
&LoadReval)) {
insertConstantsFrom(TR, *DownHypothesis);
InsertConstantValue(TR, Val);
return true;
} else {
if (LoadReval && UA != UseActivity::AllStores) {
ReEvaluateValueIfInactiveInst[LoadReval].insert(TmpOrig);
}
}
}
}
}
// otherwise if the origin is a previously derived known inactive value
// assess
// TODO here we would need to potentially consider loading an active
// global as we again assume that active memory is passed explicitly as an
// argument
if (TmpOrig != Val) {
if (isConstantValue(TR, TmpOrig)) {
if (EnzymePrintActivity)
llvm::errs() << " Potential Pointer(" << (int)directions << ") "
<< *Val << " inactive from inactive origin "
<< *TmpOrig << "\n";
InsertConstantValue(TR, Val);
return true;
}
}
if (auto inst = dyn_cast<Instruction>(Val)) {
if (!inst->mayReadFromMemory() && !isa<AllocaInst>(Val)) {
if (directions == UP && !isa<PHINode>(inst)) {
if (isInstructionInactiveFromOrigin(TR, inst)) {
InsertConstantValue(TR, Val);
return true;
}
} else {
if (UpHypothesis->isInstructionInactiveFromOrigin(TR, inst)) {
InsertConstantValue(TR, Val);
insertConstantsFrom(TR, *UpHypothesis);
return true;
}
}
}
}
}
// If not capable of looking at both users and uses, all the ways a pointer
// can be loaded/stored cannot be assesed and therefore we default to assume
// it to be active
if (directions != 3) {
if (EnzymePrintActivity)
llvm::errs() << " <Potential Pointer assumed active at "
<< (int)directions << ">" << *Val << "\n";
ActiveValues.insert(Val);
return false;
}
if (EnzymePrintActivity)
llvm::errs() << " < MEMSEARCH" << (int)directions << ">" << *Val << "\n";
// A pointer value is active if two things hold:
// an potentially active value is stored into the memory
// memory loaded from the value is used in an active way
bool potentiallyActiveStore = false;
bool potentialStore = false;
bool potentiallyActiveLoad = false;
// Assume the value (not instruction) is itself active
// In spite of that can we show that there are either no active stores
// or no active loads
std::shared_ptr<ActivityAnalyzer> Hypothesis =
std::shared_ptr<ActivityAnalyzer>(
new ActivityAnalyzer(*this, directions));
Hypothesis->ActiveValues.insert(Val);
if (auto VI = dyn_cast<Instruction>(Val)) {
if (UpHypothesis->isInstructionInactiveFromOrigin(TR, VI)) {
Hypothesis->DeducingPointers.insert(Val);
if (EnzymePrintActivity)
llvm::errs() << " constant instruction hypothesis: " << *VI << "\n";
} else {
if (EnzymePrintActivity)
llvm::errs() << " cannot show constant instruction hypothesis: "
<< *VI << "\n";
}
}
auto checkActivity = [&](Instruction *I) {
if (notForAnalysis.count(I->getParent()))
return false;
if (isa<FenceInst>(I))
return false;
// If this is a malloc or free, this doesn't impact the activity
if (auto CI = dyn_cast<CallInst>(I)) {
if (CI->hasFnAttr("enzyme_inactive"))
return false;
#if LLVM_VERSION_MAJOR >= 11
if (auto iasm = dyn_cast<InlineAsm>(CI->getCalledOperand()))
#else
if (auto iasm = dyn_cast<InlineAsm>(CI->getCalledValue()))
#endif
{
if (StringRef(iasm->getAsmString()).contains("exit") ||
StringRef(iasm->getAsmString()).contains("cpuid"))
return false;
}
Function *F = getFunctionFromCall(CI);
StringRef funcName = getFuncNameFromCall(CI);
if (F && F->hasFnAttribute("enzyme_inactive")) {
return false;
}
if (isAllocationFunction(funcName, TLI) ||
isDeallocationFunction(funcName, TLI)) {
return false;
}
if (KnownInactiveFunctions.count(funcName.str()) ||
MPIInactiveCommAllocators.find(funcName.str()) !=
MPIInactiveCommAllocators.end()) {
return false;
}
if (KnownInactiveFunctionInsts.count(funcName.str())) {
return false;
}
if (isMemFreeLibMFunction(funcName) || funcName == "__fd_sincos_1") {
return false;
}
#if LLVM_VERSION_MAJOR >= 9
auto dName = demangle(funcName.str());
for (auto FuncName : DemangledKnownInactiveFunctionsStartingWith) {
if (StringRef(dName).startswith(FuncName)) {
return false;
}
}
#endif
for (auto FuncName : KnownInactiveFunctionsStartingWith) {
if (funcName.startswith(FuncName)) {
return false;
}
}
for (auto FuncName : KnownInactiveFunctionsContains) {
if (funcName.contains(FuncName)) {
return false;
}
}
if (funcName == "__cxa_guard_acquire" ||
funcName == "__cxa_guard_release" ||
funcName == "__cxa_guard_abort" || funcName == "posix_memalign") {
return false;
}
if (F) {
switch (F->getIntrinsicID()) {
case Intrinsic::nvvm_barrier0:
case Intrinsic::nvvm_barrier0_popc:
case Intrinsic::nvvm_barrier0_and:
case Intrinsic::nvvm_barrier0_or:
case Intrinsic::nvvm_membar_cta:
case Intrinsic::nvvm_membar_gl:
case Intrinsic::nvvm_membar_sys:
case Intrinsic::amdgcn_s_barrier:
case Intrinsic::assume:
case Intrinsic::stacksave:
case Intrinsic::stackrestore:
case Intrinsic::lifetime_start:
case Intrinsic::lifetime_end:
case Intrinsic::dbg_addr:
case Intrinsic::dbg_declare:
case Intrinsic::dbg_value:
case Intrinsic::invariant_start:
case Intrinsic::invariant_end:
case Intrinsic::var_annotation:
case Intrinsic::ptr_annotation:
case Intrinsic::annotation:
case Intrinsic::codeview_annotation:
case Intrinsic::expect:
case Intrinsic::type_test:
case Intrinsic::donothing:
case Intrinsic::prefetch:
case Intrinsic::trap:
#if LLVM_VERSION_MAJOR >= 8
case Intrinsic::is_constant:
#endif
return false;
default:
break;
}
}
}
Value *memval = Val;
// BasicAA stupidy assumes that non-pointer's don't alias
// if this is a nonpointer, use something else to force alias
// consideration
if (!memval->getType()->isPointerTy()) {
if (auto ci = dyn_cast<CastInst>(Val)) {
if (ci->getOperand(0)->getType()->isPointerTy()) {
memval = ci->getOperand(0);
}
}
for (auto user : Val->users()) {
if (isa<CastInst>(user) && user->getType()->isPointerTy()) {
memval = user;
break;
}
}
}
#if LLVM_VERSION_MAJOR >= 12
auto AARes = AA.getModRefInfo(
I, MemoryLocation(memval, LocationSize::beforeOrAfterPointer()));
#elif LLVM_VERSION_MAJOR >= 9
auto AARes =
AA.getModRefInfo(I, MemoryLocation(memval, LocationSize::unknown()));
#else
auto AARes = AA.getModRefInfo(
I, MemoryLocation(memval, MemoryLocation::UnknownSize));
#endif
// Still having failed to replace the location used by AA, fall back to
// getModref against any location.
if (!memval->getType()->isPointerTy()) {
if (auto CB = dyn_cast<CallInst>(I)) {
#if LLVM_VERSION_MAJOR >= 16
AARes = AA.getMemoryEffects(CB).getModRef();
#else
AARes = createModRefInfo(AA.getModRefBehavior(CB));
#endif
} else {
bool mayRead = I->mayReadFromMemory();
bool mayWrite = I->mayWriteToMemory();
AARes = mayRead ? (mayWrite ? ModRefInfo::ModRef : ModRefInfo::Ref)
: (mayWrite ? ModRefInfo::Mod : ModRefInfo::NoModRef);
}
}
if (auto CB = dyn_cast<CallInst>(I)) {
if (CB->onlyAccessesInaccessibleMemory())
AARes = ModRefInfo::NoModRef;
}
// TODO this aliasing information is too conservative, the question
// isn't merely aliasing but whether there is a path for THIS value to
// eventually be loaded by it not simply because there isnt aliasing
// If we haven't already shown a potentially active load
// check if this loads the given value and is active
if (!potentiallyActiveLoad && isRefSet(AARes)) {
if (EnzymePrintActivity)
llvm::errs() << "potential active load: " << *I << "\n";
if (isa<LoadInst>(I) || (isa<IntrinsicInst>(I) &&
(cast<IntrinsicInst>(I)->getIntrinsicID() ==
Intrinsic::nvvm_ldu_global_i ||
cast<IntrinsicInst>(I)->getIntrinsicID() ==
Intrinsic::nvvm_ldu_global_p ||
cast<IntrinsicInst>(I)->getIntrinsicID() ==
Intrinsic::nvvm_ldu_global_f ||
cast<IntrinsicInst>(I)->getIntrinsicID() ==
Intrinsic::nvvm_ldg_global_i ||
cast<IntrinsicInst>(I)->getIntrinsicID() ==
Intrinsic::nvvm_ldg_global_p ||
cast<IntrinsicInst>(I)->getIntrinsicID() ==
Intrinsic::nvvm_ldg_global_f))) {
// If the ref'ing value is a load check if the loaded value is
// active
if (!Hypothesis->isConstantValue(TR, I)) {
potentiallyActiveLoad = true;
// returns whether seen
std::function<bool(Value * V, SmallPtrSetImpl<Value *> &)>
loadCheck = [&](Value *V, SmallPtrSetImpl<Value *> &Seen) {
if (Seen.count(V))
return false;
Seen.insert(V);
if (TR.query(V)[{-1}].isPossiblePointer()) {
for (auto UU : V->users()) {
auto U = cast<Instruction>(UU);
if (U->mayWriteToMemory()) {
if (!Hypothesis->isConstantInstruction(TR, U)) {
if (EnzymePrintActivity)
llvm::errs() << "potential active store via "
"pointer in load: "
<< *I << " of " << *Val << " via "
<< *U << "\n";
potentiallyActiveStore = true;
return true;
}
}
if (U != Val && !Hypothesis->isConstantValue(TR, U)) {
if (loadCheck(U, Seen))
return true;
}
}
}
return false;
};
SmallPtrSet<Value *, 2> Seen;
loadCheck(I, Seen);
}
} else if (auto MTI = dyn_cast<MemTransferInst>(I)) {
if (!Hypothesis->isConstantValue(TR, MTI->getArgOperand(0))) {
potentiallyActiveLoad = true;
if (TR.query(Val)[{-1, -1}].isPossiblePointer()) {
if (EnzymePrintActivity)
llvm::errs()
<< "potential active store via pointer in memcpy: " << *I
<< " of " << *Val << "\n";
potentiallyActiveStore = true;
}
}
} else {
// Otherwise fallback and check any part of the instruction is
// active
// TODO: note that this can be optimized (especially for function
// calls)
// Notably need both to check the result and instruction since
// A load that has as result an active pointer is not an active
// instruction, but does have an active value
if (!Hypothesis->isConstantInstruction(TR, I) ||
(I != Val && !Hypothesis->isConstantValue(TR, I))) {
potentiallyActiveLoad = true;
// If this a potential pointer of pointer AND
// double** Val;
//
if (TR.query(Val)[{-1, -1}].isPossiblePointer()) {
// If this instruction either:
// 1) can actively store into the inner pointer, even
// if it doesn't store into the outer pointer. Actively
// storing into the outer pointer is handled by the isMod
// case.
// I(double** readonly Val, double activeX) {
// double* V0 = Val[0]
// V0 = activeX;
// }
// 2) may return an active pointer loaded from Val
// double* I = *Val;
// I[0] = active;
//
if ((I->mayWriteToMemory() &&
!Hypothesis->isConstantInstruction(TR, I)) ||
(!Hypothesis->DeducingPointers.count(I) &&
!Hypothesis->isConstantValue(TR, I) &&
TR.query(I)[{-1}].isPossiblePointer())) {
if (EnzymePrintActivity)
llvm::errs() << "potential active store via pointer in "
"unknown inst: "
<< *I << " of " << *Val << "\n";
potentiallyActiveStore = true;
}
}
}
}
}
if ((!potentiallyActiveStore || !potentialStore) && isModSet(AARes)) {
if (EnzymePrintActivity)
llvm::errs() << "potential active store: " << *I << " Val=" << *Val
<< "\n";
if (auto SI = dyn_cast<StoreInst>(I)) {
bool cop = !Hypothesis->isConstantValue(TR, SI->getValueOperand());
if (EnzymePrintActivity)
llvm::errs() << " -- store potential activity: " << (int)cop
<< " - " << *SI << " of "
<< " Val=" << *Val << "\n";
potentialStore = true;
if (cop)
potentiallyActiveStore = true;
} else if (auto MTI = dyn_cast<MemTransferInst>(I)) {
bool cop = !Hypothesis->isConstantValue(TR, MTI->getArgOperand(1));
potentialStore = true;
if (cop)
potentiallyActiveStore = true;
} else if (isa<MemSetInst>(I)) {
potentialStore = true;
} else {
// Otherwise fallback and check if the instruction is active
// TODO: note that this can be optimized (especially for function
// calls)
auto cop = !Hypothesis->isConstantInstruction(TR, I);
if (EnzymePrintActivity)
llvm::errs() << " -- unknown store potential activity: " << (int)cop
<< " - " << *I << " of "
<< " Val=" << *Val << "\n";
potentialStore = true;
if (cop)
potentiallyActiveStore = true;
}
}
if (potentiallyActiveStore && potentiallyActiveLoad)
return true;
return false;
};
// Search through all the instructions in this function
// for potential loads / stores of this value.
//
// We can choose to only look at potential follower instructions
// if the value is created by the instruction (alloca, noalias)
// since no potentially active store to the same location can occur
// prior to its creation. Otherwise, check all instructions in the
// function as a store to an aliasing location may have occured
// prior to the instruction generating the value.
if (auto VI = dyn_cast<AllocaInst>(Val)) {
allFollowersOf(VI, checkActivity);
} else if (auto VI = dyn_cast<CallInst>(Val)) {
if (VI->hasRetAttr(Attribute::NoAlias))
allFollowersOf(VI, checkActivity);
else {
for (BasicBlock &BB : *TR.getFunction()) {
if (notForAnalysis.count(&BB))
continue;
for (Instruction &I : BB) {
if (checkActivity(&I))
goto activeLoadAndStore;
}
}
}
} else if (isa<Argument>(Val) || isa<Instruction>(Val)) {
for (BasicBlock &BB : *TR.getFunction()) {
if (notForAnalysis.count(&BB))
continue;
for (Instruction &I : BB) {
if (checkActivity(&I))
goto activeLoadAndStore;
}
}
} else {
llvm::errs() << "unknown pointer value type: " << *Val << "\n";
assert(0 && "unknown pointer value type");
llvm_unreachable("unknown pointer value type");
}
activeLoadAndStore:;
if (EnzymePrintActivity)
llvm::errs() << " </MEMSEARCH" << (int)directions << ">" << *Val
<< " potentiallyActiveLoad=" << potentiallyActiveLoad
<< " potentiallyActiveStore=" << potentiallyActiveStore
<< " potentialStore=" << potentialStore << "\n";
if (potentiallyActiveLoad && potentiallyActiveStore) {
insertAllFrom(TR, *Hypothesis, Val);
// TODO have insertall dependence on this
if (TmpOrig != Val)
ReEvaluateValueIfInactiveValue[TmpOrig].insert(Val);
return false;
} else {
// We now know that there isn't a matching active load/store pair in this
// function. Now the only way that this memory can facilitate a transfer
// of active information is if it is done outside of the function
// This can happen if either:
// a) the memory had an active load or store before this function was
// called b) the memory had an active load or store after this function
// was called
// Case a) can occur if:
// 1) this memory came from an active global
// 2) this memory came from an active argument
// 3) this memory came from a load from active memory
// In other words, assuming this value is inactive, going up this
// location's argument must be inactive
assert(UpHypothesis);
// UpHypothesis.ConstantValues.insert(val);
if (DeducingPointers.size() == 0)
UpHypothesis->insertConstantsFrom(TR, *Hypothesis);
assert(directions & UP);
bool ActiveUp = !isa<Argument>(Val) &&
!UpHypothesis->isInstructionInactiveFromOrigin(TR, Val);
// Case b) can occur if:
// 1) this memory is used as part of an active return
// 2) this memory is stored somewhere
// We never verify that an origin wasn't stored somewhere or returned.
// to remedy correctness for now let's do something extremely simple
std::shared_ptr<ActivityAnalyzer> DownHypothesis =
std::shared_ptr<ActivityAnalyzer>(new ActivityAnalyzer(*this, DOWN));
DownHypothesis->ConstantValues.insert(Val);
DownHypothesis->insertConstantsFrom(TR, *Hypothesis);
bool ActiveDown =
DownHypothesis->isValueActivelyStoredOrReturned(TR, Val);
// BEGIN TEMPORARY
if (!ActiveDown && TmpOrig != Val) {
if (isa<Argument>(TmpOrig) || isa<GlobalVariable>(TmpOrig) ||
isa<AllocaInst>(TmpOrig) || isAllocationCall(TmpOrig, TLI)) {
std::shared_ptr<ActivityAnalyzer> DownHypothesis2 =
std::shared_ptr<ActivityAnalyzer>(
new ActivityAnalyzer(*DownHypothesis, DOWN));
DownHypothesis2->ConstantValues.insert(TmpOrig);
if (DownHypothesis2->isValueActivelyStoredOrReturned(TR, TmpOrig)) {
if (EnzymePrintActivity)
llvm::errs() << " active from ivasor: " << *TmpOrig << "\n";
ActiveDown = true;
}
} else {
// unknown origin that could've been stored/returned/etc
if (EnzymePrintActivity)
llvm::errs() << " active from unknown origin: " << *TmpOrig << "\n";
ActiveDown = true;
}
}
// END TEMPORARY
// We can now consider the three places derivative information can be
// transferred
// Case A) From the origin
// Case B) Though the return
// Case C) Within the function (via either load or store)
bool ActiveMemory = false;
// If it is transferred via active origin and return, clearly this is
// active
ActiveMemory |= (ActiveUp && ActiveDown);
// If we come from an active origin and load, memory is clearly active
ActiveMemory |= (ActiveUp && potentiallyActiveLoad);
// If we come from an active origin and only store into it, it changes
// future state
ActiveMemory |= (ActiveUp && potentialStore);
// If we go to an active return and store active memory, this is active
ActiveMemory |= (ActiveDown && potentialStore);
// Actually more generally, if we are ActiveDown (returning memory that is
// used) in active return, we must be active. This is necessary to ensure
// mallocs have their differential shadows created when returned [TODO
// investigate more]
ActiveMemory |= ActiveDown;
// If we go to an active return and only load it, however, that doesnt
// transfer derivatives and we can say this memory is inactive
if (EnzymePrintActivity)
llvm::errs() << " @@MEMSEARCH" << (int)directions << ">" << *Val
<< " potentiallyActiveLoad=" << potentiallyActiveLoad
<< " potentialStore=" << potentialStore
<< " ActiveUp=" << ActiveUp << " ActiveDown=" << ActiveDown
<< " ActiveMemory=" << ActiveMemory << "\n";
if (ActiveMemory) {
ActiveValues.insert(Val);
assert(Hypothesis->directions == directions);
assert(Hypothesis->ActiveValues.count(Val));
insertAllFrom(TR, *Hypothesis, Val);
if (TmpOrig != Val)
ReEvaluateValueIfInactiveValue[TmpOrig].insert(Val);
return false;
} else {
InsertConstantValue(TR, Val);
insertConstantsFrom(TR, *Hypothesis);
if (DeducingPointers.size() == 0)
insertConstantsFrom(TR, *UpHypothesis);
insertConstantsFrom(TR, *DownHypothesis);
return true;
}
}
}
// For all non-pointers, it is now sufficient to simply prove that
// either activity does not flow in, or activity does not flow out
// This alone cuts off the flow (being unable to flow through memory)
// Not looking at uses to prove inactive (definition of up), if the creator of
// this value is inactive, we are inactive Since we won't look at uses to
// prove, we can inductively assume this is inactive
if (directions & UP) {
if (directions == UP && !isa<PHINode>(Val)) {
if (isInstructionInactiveFromOrigin(TR, Val)) {
InsertConstantValue(TR, Val);
return true;
} else if (auto I = dyn_cast<Instruction>(Val)) {
if (directions == 3) {
for (auto &op : I->operands()) {
if (!UpHypothesis->isConstantValue(TR, op)) {
ReEvaluateValueIfInactiveValue[op].insert(I);
}
}
}
}
} else {
UpHypothesis =
std::shared_ptr<ActivityAnalyzer>(new ActivityAnalyzer(*this, UP));
UpHypothesis->ConstantValues.insert(Val);
if (UpHypothesis->isInstructionInactiveFromOrigin(TR, Val)) {
insertConstantsFrom(TR, *UpHypothesis);
InsertConstantValue(TR, Val);
return true;
} else if (auto I = dyn_cast<Instruction>(Val)) {
if (directions == 3) {
for (auto &op : I->operands()) {
if (!UpHypothesis->isConstantValue(TR, op)) {
ReEvaluateValueIfInactiveValue[op].insert(I);
}
}
}
}
}
}
if (directions & DOWN) {
// Not looking at users to prove inactive (definition of down)
// If all users are inactive, this is therefore inactive.
// Since we won't look at origins to prove, we can inductively assume this
// is inactive
// As an optimization if we are going down already
// and we won't use ourselves (done by PHI's), we
// dont need to inductively assume we're true
// and can instead use this object!
if (directions == DOWN && !isa<PHINode>(Val)) {
if (isValueInactiveFromUsers(TR, Val, UseActivity::None)) {
if (UpHypothesis)
insertConstantsFrom(TR, *UpHypothesis);
InsertConstantValue(TR, Val);
return true;
}
} else {
auto DownHypothesis =
std::shared_ptr<ActivityAnalyzer>(new ActivityAnalyzer(*this, DOWN));
DownHypothesis->ConstantValues.insert(Val);
if (DownHypothesis->isValueInactiveFromUsers(TR, Val,
UseActivity::None)) {
insertConstantsFrom(TR, *DownHypothesis);
if (UpHypothesis)
insertConstantsFrom(TR, *UpHypothesis);
InsertConstantValue(TR, Val);
return true;
}
}
}
if (EnzymePrintActivity)
llvm::errs() << " Value nonconstant (couldn't disprove)[" << (int)directions
<< "]" << *Val << "\n";
ActiveValues.insert(Val);
return false;
}
/// Is the instruction guaranteed to be inactive because of its operands
bool ActivityAnalyzer::isInstructionInactiveFromOrigin(TypeResults const &TR,
llvm::Value *val) {
// Must be an analyzer only searching up
assert(directions == UP);
assert(!isa<Argument>(val));
assert(!isa<GlobalVariable>(val));
// Not an instruction and thus not legal to search for activity via operands
if (!isa<Instruction>(val)) {
llvm::errs() << "unknown pointer source: " << *val << "\n";
assert(0 && "unknown pointer source");
llvm_unreachable("unknown pointer source");
return false;
}
Instruction *inst = cast<Instruction>(val);
if (EnzymePrintActivity)
llvm::errs() << " < UPSEARCH" << (int)directions << ">" << *inst << "\n";
// cpuid is explicitly an inactive instruction
if (auto call = dyn_cast<CallInst>(inst)) {
#if LLVM_VERSION_MAJOR >= 11
if (auto iasm = dyn_cast<InlineAsm>(call->getCalledOperand())) {
#else
if (auto iasm = dyn_cast<InlineAsm>(call->getCalledValue())) {
#endif
if (StringRef(iasm->getAsmString()).contains("cpuid")) {
if (EnzymePrintActivity)
llvm::errs() << " constant instruction from known cpuid instruction "
<< *inst << "\n";
return true;
}
}
}
if (auto SI = dyn_cast<StoreInst>(inst)) {
// if either src or dst is inactive, there cannot be a transfer of active
// values and thus the store is inactive
if (isConstantValue(TR, SI->getValueOperand()) ||
isConstantValue(TR, SI->getPointerOperand())) {
if (EnzymePrintActivity)
llvm::errs() << " constant instruction as store operand is inactive "
<< *inst << "\n";
return true;
}
}
if (auto MTI = dyn_cast<MemTransferInst>(inst)) {
// if either src or dst is inactive, there cannot be a transfer of active
// values and thus the store is inactive
if (isConstantValue(TR, MTI->getArgOperand(0)) ||
isConstantValue(TR, MTI->getArgOperand(1))) {
if (EnzymePrintActivity)
llvm::errs() << " constant instruction as memtransfer " << *inst
<< "\n";
return true;
}
}
if (auto op = dyn_cast<CallInst>(inst)) {
if (op->hasFnAttr("enzyme_inactive")) {
return true;
}
// Calls to print/assert/cxa guard are definitionally inactive
llvm::Value *callVal;
#if LLVM_VERSION_MAJOR >= 11
callVal = op->getCalledOperand();
#else
callVal = op->getCalledValue();
#endif
StringRef funcName = getFuncNameFromCall(op);
Function *called = getFunctionFromCall(op);
if (called && called->hasFnAttribute("enzyme_inactive")) {
return true;
}
if (funcName == "free" || funcName == "_ZdlPv" || funcName == "_ZdlPvm" ||
funcName == "munmap") {
return true;
}
#if LLVM_VERSION_MAJOR >= 9
auto dName = demangle(funcName.str());
for (auto FuncName : DemangledKnownInactiveFunctionsStartingWith) {
if (StringRef(dName).startswith(FuncName)) {
return true;
}
}
#endif
for (auto FuncName : KnownInactiveFunctionsStartingWith) {
if (funcName.startswith(FuncName)) {
return true;
}
}
for (auto FuncName : KnownInactiveFunctionsContains) {
if (funcName.contains(FuncName)) {
return true;
}
}
if (KnownInactiveFunctions.count(funcName.str()) ||
MPIInactiveCommAllocators.find(funcName.str()) !=
MPIInactiveCommAllocators.end()) {
if (EnzymePrintActivity)
llvm::errs() << "constant(" << (int)directions
<< ") up-knowninactivecall " << *inst << "\n";
return true;
}
if (called && called->getIntrinsicID() == Intrinsic::trap)
return true;
// If requesting empty unknown functions to be considered inactive, abide
// by those rules
if (called && EnzymeEmptyFnInactive && called->empty() &&
!hasMetadata(called, "enzyme_gradient") &&
!hasMetadata(called, "enzyme_derivative") &&
!isAllocationFunction(funcName, TLI) &&
!isDeallocationFunction(funcName, TLI) && !isa<IntrinsicInst>(op)) {
if (EnzymePrintActivity)
llvm::errs() << "constant(" << (int)directions << ") up-emptyconst "
<< *inst << "\n";
return true;
}
if (!isa<Constant>(callVal) && isConstantValue(TR, callVal)) {
if (EnzymePrintActivity)
llvm::errs() << "constant(" << (int)directions << ") up-constfn "
<< *inst << " - " << *callVal << "\n";
return true;
}
}
// Intrinsics known always to be inactive
if (auto II = dyn_cast<IntrinsicInst>(inst)) {
switch (II->getIntrinsicID()) {
case Intrinsic::nvvm_barrier0:
case Intrinsic::nvvm_barrier0_popc:
case Intrinsic::nvvm_barrier0_and:
case Intrinsic::nvvm_barrier0_or:
case Intrinsic::nvvm_membar_cta:
case Intrinsic::nvvm_membar_gl:
case Intrinsic::nvvm_membar_sys:
case Intrinsic::amdgcn_s_barrier:
case Intrinsic::assume:
case Intrinsic::stacksave:
case Intrinsic::stackrestore:
case Intrinsic::lifetime_start:
case Intrinsic::lifetime_end:
case Intrinsic::dbg_addr:
case Intrinsic::dbg_declare:
case Intrinsic::dbg_value:
case Intrinsic::invariant_start:
case Intrinsic::invariant_end:
case Intrinsic::var_annotation:
case Intrinsic::ptr_annotation:
case Intrinsic::annotation:
case Intrinsic::codeview_annotation:
case Intrinsic::expect:
case Intrinsic::type_test:
case Intrinsic::donothing:
case Intrinsic::prefetch:
#if LLVM_VERSION_MAJOR >= 8
case Intrinsic::is_constant:
#endif
case Intrinsic::memset:
if (EnzymePrintActivity)
llvm::errs() << "constant(" << (int)directions << ") up-intrinsic "
<< *inst << "\n";
return true;
default:
break;
}
}
if (auto gep = dyn_cast<GetElementPtrInst>(inst)) {
// A gep's only args that could make it active is the pointer operand
if (isConstantValue(TR, gep->getPointerOperand())) {
if (EnzymePrintActivity)
llvm::errs() << "constant(" << (int)directions << ") up-gep " << *inst
<< "\n";
return true;
}
return false;
} else if (auto ci = dyn_cast<CallInst>(inst)) {
bool seenuse = false;
propagateArgumentInformation(TLI, *ci, [&](Value *a) {
if (!isConstantValue(TR, a)) {
seenuse = true;
if (EnzymePrintActivity)
llvm::errs() << "nonconstant(" << (int)directions << ") up-call "
<< *inst << " op " << *a << "\n";
return true;
}
return false;
});
if (EnzymeGlobalActivity) {
if (!ci->onlyAccessesArgMemory() && !ci->doesNotAccessMemory()) {
bool legalUse = false;
StringRef funcName = getFuncNameFromCall(ci);
if (funcName == "") {
} else if (isMemFreeLibMFunction(funcName) ||
isDebugFunction(ci->getCalledFunction()) ||
isCertainPrint(funcName) ||
isAllocationFunction(funcName, TLI) ||
isDeallocationFunction(funcName, TLI)) {
legalUse = true;
}
if (!legalUse) {
if (EnzymePrintActivity)
llvm::errs() << "nonconstant(" << (int)directions << ") up-global "
<< *inst << "\n";
seenuse = true;
}
}
}
if (!seenuse) {
if (EnzymePrintActivity)
llvm::errs() << "constant(" << (int)directions << ") up-call:" << *inst
<< "\n";
return true;
}
return !seenuse;
} else if (auto si = dyn_cast<SelectInst>(inst)) {
if (isConstantValue(TR, si->getTrueValue()) &&
isConstantValue(TR, si->getFalseValue())) {
if (EnzymePrintActivity)
llvm::errs() << "constant(" << (int)directions << ") up-sel:" << *inst
<< "\n";
return true;
}
return false;
} else if (isa<SIToFPInst>(inst) || isa<UIToFPInst>(inst) ||
isa<FPToSIInst>(inst) || isa<FPToUIInst>(inst)) {
if (EnzymePrintActivity)
llvm::errs() << "constant(" << (int)directions << ") up-fpcst:" << *inst
<< "\n";
return true;
} else {
bool seenuse = false;
//! TODO does not consider reading from global memory that is active and not
//! an argument
for (auto &a : inst->operands()) {
bool hypval = isConstantValue(TR, a);
if (!hypval) {
if (EnzymePrintActivity)
llvm::errs() << "nonconstant(" << (int)directions << ") up-inst "
<< *inst << " op " << *a << "\n";
seenuse = true;
break;
}
}
if (!seenuse) {
if (EnzymePrintActivity)
llvm::errs() << "constant(" << (int)directions << ") up-inst:" << *inst
<< "\n";
return true;
}
return false;
}
}
/// Is the value free of any active uses
bool ActivityAnalyzer::isValueInactiveFromUsers(TypeResults const &TR,
llvm::Value *const val,
UseActivity PUA,
Instruction **FoundInst) {
assert(directions & DOWN);
// Must be an analyzer only searching down, unless used outside
// assert(directions == DOWN);
// To ensure we can call down
if (EnzymePrintActivity)
llvm::errs() << " <Value USESEARCH" << (int)directions << ">" << *val
<< " UA=" << (int)PUA << "\n";
bool seenuse = false;
// user, predecessor
std::deque<std::tuple<User *, Value *, UseActivity>> todo;
for (const auto a : val->users()) {
todo.push_back(std::make_tuple(a, val, PUA));
}
std::set<std::tuple<User *, Value *, UseActivity>> done = {};
SmallSet<Value *, 1> AllocaSet;
if (isa<AllocaInst>(val))
AllocaSet.insert(val);
if (PUA == UseActivity::None && isAllocationCall(val, TLI))
AllocaSet.insert(val);
while (todo.size()) {
auto pair = todo.front();
todo.pop_front();
if (done.count(pair))
continue;
done.insert(pair);
User *a = std::get<0>(pair);
Value *parent = std::get<1>(pair);
UseActivity UA = std::get<2>(pair);
if (auto LI = dyn_cast<LoadInst>(a)) {
if (UA == UseActivity::OnlyStores)
continue;
if (UA == UseActivity::OnlyNonPointerStores ||
UA == UseActivity::AllStores) {
if (!TR.query(LI)[{-1}].isPossiblePointer())
continue;
}
}
if (EnzymePrintActivity)
llvm::errs() << " considering use of " << *val << " - " << *a
<< "\n";
// Only ignore stores to the operand, not storing the operand
// somewhere
if (auto SI = dyn_cast<StoreInst>(a)) {
if (SI->getValueOperand() != parent) {
if (UA == UseActivity::OnlyLoads) {
continue;
}
if (UA != UseActivity::AllStores &&
(ConstantValues.count(SI->getValueOperand()) ||
isa<ConstantInt>(SI->getValueOperand())))
continue;
if (UA == UseActivity::None) {
// If storing into itself, all potential uses are taken care of
// elsewhere in the recursion.
bool shouldContinue = true;
SmallVector<Value *, 1> vtodo = {SI->getValueOperand()};
SmallSet<Value *, 1> seen;
SmallSet<Value *, 1> newAllocaSet;
while (vtodo.size()) {
auto TmpOrig = vtodo.back();
vtodo.pop_back();
if (seen.count(TmpOrig))
continue;
seen.insert(TmpOrig);
if (AllocaSet.count(TmpOrig)) {
continue;
}
if (isa<AllocaInst>(TmpOrig)) {
newAllocaSet.insert(TmpOrig);
continue;
}
if (isAllocationCall(TmpOrig, TLI)) {
newAllocaSet.insert(TmpOrig);
continue;
}
if (isa<UndefValue>(TmpOrig) || isa<ConstantInt>(TmpOrig) ||
isa<ConstantPointerNull>(TmpOrig) || isa<ConstantFP>(TmpOrig)) {
continue;
}
if (auto LI = dyn_cast<LoadInst>(TmpOrig)) {
vtodo.push_back(LI->getPointerOperand());
continue;
}
if (auto CD = dyn_cast<ConstantDataSequential>(TmpOrig)) {
for (size_t i = 0, len = CD->getNumElements(); i < len; i++)
vtodo.push_back(CD->getElementAsConstant(i));
continue;
}
if (auto CD = dyn_cast<ConstantAggregate>(TmpOrig)) {
for (size_t i = 0, len = CD->getNumOperands(); i < len; i++)
vtodo.push_back(CD->getOperand(i));
continue;
}
if (auto GV = dyn_cast<GlobalVariable>(TmpOrig)) {
// If operating under the assumption globals are inactive unless
// explicitly marked as active, this is inactive
if (!hasMetadata(GV, "enzyme_shadow") &&
EnzymeNonmarkedGlobalsInactive) {
continue;
}
if (hasMetadata(GV, "enzyme_inactive")) {
continue;
}
if (GV->getName().contains("enzyme_const") ||
InactiveGlobals.count(GV->getName().str())) {
continue;
}
}
auto TmpOrig_2 =
#if LLVM_VERSION_MAJOR >= 12
getUnderlyingObject(TmpOrig, 100);
#else
GetUnderlyingObject(
TmpOrig, TR.getFunction()->getParent()->getDataLayout(),
100);
#endif
if (TmpOrig != TmpOrig_2) {
vtodo.push_back(TmpOrig_2);
continue;
}
if (EnzymePrintActivity)
llvm::errs() << " -- cannot continuing indirect store from "
<< *val << " due to " << *TmpOrig << "\n";
shouldContinue = false;
break;
}
if (shouldContinue) {
if (EnzymePrintActivity)
llvm::errs() << " -- continuing indirect store from " << *val
<< " into:\n";
done.insert(std::make_tuple((User *)SI, SI->getValueOperand(), UA));
for (auto TmpOrig : newAllocaSet) {
for (const auto a : TmpOrig->users()) {
todo.push_back(std::make_tuple(a, TmpOrig, UA));
if (EnzymePrintActivity)
llvm::errs() << " ** " << *a << "\n";
}
AllocaSet.insert(TmpOrig);
shouldContinue = true;
}
continue;
}
}
}
if (SI->getPointerOperand() != parent) {
auto TmpOrig = SI->getPointerOperand();
// If storing into itself, all potential uses are taken care of
// elsewhere in the recursion.
bool shouldContinue = false;
while (1) {
if (AllocaSet.count(TmpOrig)) {
shouldContinue = true;
break;
}
if (isa<AllocaInst>(TmpOrig)) {
done.insert(
std::make_tuple((User *)SI, SI->getPointerOperand(), UA));
for (const auto a : TmpOrig->users()) {
todo.push_back(std::make_tuple(a, TmpOrig, UA));
}
AllocaSet.insert(TmpOrig);
shouldContinue = true;
break;
}
if (PUA == UseActivity::None) {
if (auto LI = dyn_cast<LoadInst>(TmpOrig)) {
TmpOrig = LI->getPointerOperand();
continue;
}
if (isAllocationCall(TmpOrig, TLI)) {
done.insert(
std::make_tuple((User *)SI, SI->getPointerOperand(), UA));
for (const auto a : TmpOrig->users()) {
todo.push_back(std::make_tuple(a, TmpOrig, UA));
}
AllocaSet.insert(TmpOrig);
shouldContinue = true;
break;
}
}
auto TmpOrig_2 =
#if LLVM_VERSION_MAJOR >= 12
getUnderlyingObject(TmpOrig, 100);
#else
GetUnderlyingObject(
TmpOrig, TR.getFunction()->getParent()->getDataLayout(), 100);
#endif
if (TmpOrig != TmpOrig_2) {
TmpOrig = TmpOrig_2;
continue;
}
break;
}
if (shouldContinue) {
if (EnzymePrintActivity)
llvm::errs() << " -- continuing indirect store2 from " << *val
<< " via " << *TmpOrig << "\n";
continue;
}
}
if (PUA == UseActivity::OnlyLoads) {
auto TmpOrig =
#if LLVM_VERSION_MAJOR >= 12
getUnderlyingObject(SI->getPointerOperand(), 100);
#else
GetUnderlyingObject(SI->getPointerOperand(),
TR.getFunction()->getParent()->getDataLayout(),
100);
#endif
if (TmpOrig == val) {
continue;
}
}
}
if (!isa<Instruction>(a)) {
if (auto CE = dyn_cast<ConstantExpr>(a)) {
for (auto u : CE->users()) {
todo.push_back(std::make_tuple(u, (Value *)CE, UA));
}
continue;
}
if (isa<ConstantData>(a)) {
continue;
}
if (EnzymePrintActivity)
llvm::errs() << " unknown non instruction use of " << *val << " - "
<< *a << "\n";
return false;
}
if (isa<AllocaInst>(a)) {
if (EnzymePrintActivity)
llvm::errs() << "found constant(" << (int)directions
<< ") allocainst use:" << *val << " user " << *a << "\n";
continue;
}
if (isa<SIToFPInst>(a) || isa<UIToFPInst>(a) || isa<FPToSIInst>(a) ||
isa<FPToUIInst>(a)) {
if (EnzymePrintActivity)
llvm::errs() << "found constant(" << (int)directions
<< ") si-fp use:" << *val << " user " << *a << "\n";
continue;
}
// if this instruction is in a different function, conservatively assume
// it is active
Function *InstF = cast<Instruction>(a)->getParent()->getParent();
while (PPC.CloneOrigin.find(InstF) != PPC.CloneOrigin.end())
InstF = PPC.CloneOrigin[InstF];
Function *F = TR.getFunction();
while (PPC.CloneOrigin.find(F) != PPC.CloneOrigin.end())
F = PPC.CloneOrigin[F];
if (InstF != F) {
if (EnzymePrintActivity)
llvm::errs() << "found use in different function(" << (int)directions
<< ") val:" << *val << " user " << *a << " in "
<< InstF->getName() << "@" << InstF
<< " self: " << F->getName() << "@" << F << "\n";
return false;
}
if (cast<Instruction>(a)->getParent()->getParent() != TR.getFunction())
continue;
// This use is only active if specified
if (isa<ReturnInst>(a)) {
if (ActiveReturns == DIFFE_TYPE::CONSTANT &&
UA != UseActivity::AllStores) {
continue;
} else {
return false;
}
}
if (auto call = dyn_cast<CallInst>(a)) {
bool ConstantArg = isFunctionArgumentConstant(call, parent);
if (ConstantArg && UA != UseActivity::AllStores) {
if (EnzymePrintActivity) {
llvm::errs() << "Value found constant callinst use:" << *val
<< " user " << *call << "\n";
}
continue;
}
if (Function *F = getFunctionFromCall(call)) {
if (UA == UseActivity::AllStores &&
F->getName() == "julia.write_barrier")
continue;
if (F->getIntrinsicID() == Intrinsic::memcpy ||
F->getIntrinsicID() == Intrinsic::memmove) {
// copies of constant string data do not impact activity.
if (auto cexpr = dyn_cast<ConstantExpr>(call->getArgOperand(1))) {
if (cexpr->getOpcode() == Instruction::GetElementPtr) {
if (auto GV = dyn_cast<GlobalVariable>(cexpr->getOperand(0))) {
if (GV->hasInitializer() && GV->isConstant()) {
if (auto CDA =
dyn_cast<ConstantDataArray>(GV->getInitializer())) {
if (CDA->getType()->getElementType()->isIntegerTy(8))
continue;
}
}
}
}
}
// Only need to care about loads from
if (UA == UseActivity::OnlyLoads && call->getArgOperand(1) != parent)
continue;
// Only need to care about store from
if (call->getArgOperand(0) != parent) {
if (UA == UseActivity::OnlyStores)
continue;
else if (UA == UseActivity::OnlyNonPointerStores ||
UA == UseActivity::AllStores) {
// todo can change this to query either -1 (all mem) or 0..size
// (if size of copy is const)
if (!TR.query(call->getArgOperand(1))[{-1, -1}]
.isPossiblePointer())
continue;
}
}
bool shouldContinue = false;
if (UA != UseActivity::AllStores)
for (int arg = 0; arg < 2; arg++)
if (call->getArgOperand(arg) != parent &&
(arg == 0 || (PUA == UseActivity::None))) {
Value *TmpOrig = call->getOperand(arg);
while (1) {
if (AllocaSet.count(TmpOrig)) {
shouldContinue = true;
break;
}
if (isa<AllocaInst>(TmpOrig)) {
done.insert(std::make_tuple((User *)call,
call->getArgOperand(arg), UA));
for (const auto a : TmpOrig->users()) {
todo.push_back(std::make_tuple(a, TmpOrig, UA));
}
AllocaSet.insert(TmpOrig);
shouldContinue = true;
break;
}
if (PUA == UseActivity::None) {
if (auto LI = dyn_cast<LoadInst>(TmpOrig)) {
TmpOrig = LI->getPointerOperand();
continue;
}
if (isAllocationCall(TmpOrig, TLI)) {
done.insert(std::make_tuple(
(User *)call, call->getArgOperand(arg), UA));
for (const auto a : TmpOrig->users()) {
todo.push_back(std::make_tuple(a, TmpOrig, UA));
}
AllocaSet.insert(TmpOrig);
shouldContinue = true;
break;
}
}
auto TmpOrig_2 =
#if LLVM_VERSION_MAJOR >= 12
getUnderlyingObject(TmpOrig, 100);
#else
GetUnderlyingObject(
TmpOrig,
TR.getFunction()->getParent()->getDataLayout(), 100);
#endif
if (TmpOrig != TmpOrig_2) {
TmpOrig = TmpOrig_2;
continue;
}
break;
}
if (shouldContinue)
break;
}
if (shouldContinue)
continue;
}
} else if (PUA == UseActivity::None || PUA == UseActivity::OnlyStores) {
// If calling a function derived from an alloca of this value,
// the function is only active if the function stored into
// the allocation is active (all functions not explicitly marked
// inactive), or one of the args to the call is active
#if LLVM_VERSION_MAJOR >= 11
Value *operand = call->getCalledOperand();
#else
Value *operand = call->getCalledValue();
#endif
bool toContinue = false;
if (isa<LoadInst>(operand)) {
bool legal = true;
#if LLVM_VERSION_MAJOR >= 14
for (unsigned i = 0; i < call->arg_size() + 1; ++i)
#else
for (unsigned i = 0; i < call->getNumArgOperands() + 1; ++i)
#endif
{
Value *a = call->getOperand(i);
if (isa<ConstantInt>(a))
continue;
Value *ptr = a;
bool subValue = false;
while (ptr) {
auto TmpOrig2 =
#if LLVM_VERSION_MAJOR >= 12
getUnderlyingObject(ptr, 100);
#else
GetUnderlyingObject(
ptr, TR.getFunction()->getParent()->getDataLayout(), 100);
#endif
if (AllocaSet.count(TmpOrig2)) {
subValue = true;
break;
}
if (isa<AllocaInst>(TmpOrig2)) {
done.insert(std::make_tuple((User *)call, a, UA));
for (const auto a : TmpOrig2->users()) {
todo.push_back(std::make_tuple(a, TmpOrig2, UA));
}
AllocaSet.insert(TmpOrig2);
subValue = true;
break;
}
if (PUA == UseActivity::None) {
if (isAllocationCall(TmpOrig2, TLI)) {
done.insert(std::make_tuple((User *)call, a, UA));
for (const auto a : TmpOrig2->users()) {
todo.push_back(std::make_tuple(a, TmpOrig2, UA));
}
AllocaSet.insert(TmpOrig2);
subValue = true;
break;
}
if (auto L = dyn_cast<LoadInst>(TmpOrig2)) {
ptr = L->getPointerOperand();
} else
ptr = nullptr;
} else
ptr = nullptr;
}
if (subValue)
continue;
legal = false;
break;
}
if (legal) {
toContinue = true;
break;
}
}
if (toContinue)
continue;
}
}
// For an inbound gep, args which are not the pointer being offset
// are not used in an active way by definition.
if (auto gep = dyn_cast<GetElementPtrInst>(a)) {
if (gep->isInBounds() && gep->getPointerOperand() != parent)
continue;
}
// If this doesn't write to memory this can only be an active use
// if its return is used in an active way, therefore add this to
// the list of users to analyze
if (auto I = dyn_cast<Instruction>(a)) {
if (notForAnalysis.count(I->getParent())) {
if (EnzymePrintActivity) {
llvm::errs() << "Value found constant unreachable inst use:" << *val
<< " user " << *I << "\n";
}
continue;
}
if (UA != UseActivity::AllStores && ConstantInstructions.count(I)) {
if (I->getType()->isVoidTy() || I->getType()->isTokenTy() ||
ConstantValues.count(I)) {
if (EnzymePrintActivity) {
llvm::errs() << "Value found constant inst use:" << *val << " user "
<< *I << "\n";
}
continue;
}
UseActivity NU = UA;
if (UA == UseActivity::OnlyLoads || UA == UseActivity::OnlyStores ||
UA == UseActivity::OnlyNonPointerStores) {
if (!isa<PHINode>(I) && !isa<CastInst>(I) &&
!isa<GetElementPtrInst>(I) && !isa<BinaryOperator>(I))
NU = UseActivity::None;
}
for (auto u : I->users()) {
todo.push_back(std::make_tuple(u, (Value *)I, NU));
}
continue;
}
if (!I->mayWriteToMemory() || isa<LoadInst>(I)) {
if (TR.query(I)[{-1}].isIntegral()) {
continue;
}
UseActivity NU = UA;
if (UA == UseActivity::OnlyLoads || UA == UseActivity::OnlyStores ||
UA == UseActivity::OnlyNonPointerStores) {
if (!isa<PHINode>(I) && !isa<CastInst>(I) &&
!isa<GetElementPtrInst>(I) && !isa<BinaryOperator>(I))
NU = UseActivity::None;
}
for (auto u : I->users()) {
todo.push_back(std::make_tuple(u, (Value *)I, NU));
}
continue;
}
if (FoundInst)
*FoundInst = I;
}
if (EnzymePrintActivity)
llvm::errs() << "Value nonconstant inst (uses):" << *val << " user " << *a
<< "\n";
seenuse = true;
break;
}
if (EnzymePrintActivity)
llvm::errs() << " </Value USESEARCH" << (int)directions
<< " const=" << (!seenuse) << ">" << *val << "\n";
return !seenuse;
}
/// Is the value potentially actively returned or stored
bool ActivityAnalyzer::isValueActivelyStoredOrReturned(TypeResults const &TR,
llvm::Value *val,
bool outside) {
// Must be an analyzer only searching down
if (!outside)
assert(directions == DOWN);
bool ignoreStoresInto = true;
auto key = std::make_pair(ignoreStoresInto, val);
if (StoredOrReturnedCache.find(key) != StoredOrReturnedCache.end()) {
return StoredOrReturnedCache[key];
}
if (EnzymePrintActivity)
llvm::errs() << " <ASOR" << (int)directions
<< " ignoreStoresinto=" << ignoreStoresInto << ">" << *val
<< "\n";
StoredOrReturnedCache[key] = false;
for (const auto a : val->users()) {
if (isa<AllocaInst>(a)) {
continue;
}
// Loading a value prevents its pointer from being captured
if (isa<LoadInst>(a)) {
continue;
}
if (isa<ReturnInst>(a)) {
if (ActiveReturns == DIFFE_TYPE::CONSTANT)
continue;
if (EnzymePrintActivity)
llvm::errs() << " </ASOR" << (int)directions
<< " ignoreStoresInto=" << ignoreStoresInto << ">"
<< " active from-ret>" << *val << "\n";
StoredOrReturnedCache[key] = true;
return true;
}
if (auto call = dyn_cast<CallInst>(a)) {
if (!couldFunctionArgumentCapture(call, val)) {
continue;
}
bool ConstantArg = isFunctionArgumentConstant(call, val);
if (ConstantArg) {
continue;
}
}
if (auto SI = dyn_cast<StoreInst>(a)) {
// If we are being stored into, not storing this value
// this case can be skipped
if (SI->getValueOperand() != val) {
if (!ignoreStoresInto) {
// Storing into active value, return true
if (!isConstantValue(TR, SI->getValueOperand())) {
StoredOrReturnedCache[key] = true;
if (EnzymePrintActivity)
llvm::errs() << " </ASOR" << (int)directions
<< " ignoreStoresInto=" << ignoreStoresInto
<< " active from-store>" << *val
<< " store into=" << *SI << "\n";
return true;
}
}
continue;
} else {
// Storing into active memory, return true
if (!isConstantValue(TR, SI->getPointerOperand())) {
StoredOrReturnedCache[key] = true;
if (EnzymePrintActivity)
llvm::errs() << " </ASOR" << (int)directions
<< " ignoreStoresInto=" << ignoreStoresInto
<< " active from-store>" << *val << " store=" << *SI
<< "\n";
return true;
}
continue;
}
}
if (auto inst = dyn_cast<Instruction>(a)) {
if (!inst->mayWriteToMemory() ||
(isa<CallInst>(inst) && AA.onlyReadsMemory(cast<CallInst>(inst)))) {
// if not written to memory and returning a known constant, this
// cannot be actively returned/stored
if (inst->getParent()->getParent() == TR.getFunction() &&
isConstantValue(TR, a)) {
continue;
}
// if not written to memory and returning a value itself
// not actively stored or returned, this is not actively
// stored or returned
if (!isValueActivelyStoredOrReturned(TR, a, outside)) {
continue;
}
}
}
if (isAllocationCall(a, TLI)) {
// if not written to memory and returning a known constant, this
// cannot be actively returned/stored
if (isConstantValue(TR, a)) {
continue;
}
// if not written to memory and returning a value itself
// not actively stored or returned, this is not actively
// stored or returned
if (!isValueActivelyStoredOrReturned(TR, a, outside)) {
continue;
}
} else if (isDeallocationCall(a, TLI)) {
// freeing memory never counts
continue;
}
// fallback and conservatively assume that if the value is written to
// it is written to active memory
// TODO handle more memory instructions above to be less conservative
if (EnzymePrintActivity)
llvm::errs() << " </ASOR" << (int)directions
<< " ignoreStoresInto=" << ignoreStoresInto
<< " active from-unknown>" << *val << " - use=" << *a
<< "\n";
return StoredOrReturnedCache[key] = true;
}
if (EnzymePrintActivity)
llvm::errs() << " </ASOR" << (int)directions
<< " ignoreStoresInto=" << ignoreStoresInto << " inactive>"
<< *val << "\n";
return false;
}
void ActivityAnalyzer::InsertConstantInstruction(TypeResults const &TR,
llvm::Instruction *I) {
ConstantInstructions.insert(I);
auto found = ReEvaluateValueIfInactiveInst.find(I);
if (found == ReEvaluateValueIfInactiveInst.end())
return;
auto set = std::move(ReEvaluateValueIfInactiveInst[I]);
ReEvaluateValueIfInactiveInst.erase(I);
for (auto toeval : set) {
if (!ActiveValues.count(toeval))
continue;
ActiveValues.erase(toeval);
if (EnzymePrintActivity)
llvm::errs() << " re-evaluating activity of val " << *toeval
<< " due to inst " << *I << "\n";
isConstantValue(TR, toeval);
}
}
void ActivityAnalyzer::InsertConstantValue(TypeResults const &TR,
llvm::Value *V) {
ConstantValues.insert(V);
auto found = ReEvaluateValueIfInactiveValue.find(V);
if (found != ReEvaluateValueIfInactiveValue.end()) {
auto set = std::move(ReEvaluateValueIfInactiveValue[V]);
ReEvaluateValueIfInactiveValue.erase(V);
for (auto toeval : set) {
if (!ActiveValues.count(toeval))
continue;
ActiveValues.erase(toeval);
if (EnzymePrintActivity)
llvm::errs() << " re-evaluating activity of val " << *toeval
<< " due to value " << *V << "\n";
isConstantValue(TR, toeval);
}
}
auto found2 = ReEvaluateInstIfInactiveValue.find(V);
if (found2 != ReEvaluateInstIfInactiveValue.end()) {
auto set = std::move(ReEvaluateInstIfInactiveValue[V]);
ReEvaluateInstIfInactiveValue.erase(V);
for (auto toeval : set) {
if (!ActiveInstructions.count(toeval))
continue;
ActiveInstructions.erase(toeval);
if (EnzymePrintActivity)
llvm::errs() << " re-evaluating activity of inst " << *toeval
<< " due to value " << *V << "\n";
isConstantInstruction(TR, toeval);
}
}
}