//===--- LinearMapInfo.cpp ------------------------------------*- C++ -*---===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2019 - 2020 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
//
// Linear map struct and branching trace enum information for differentation.
//
//===----------------------------------------------------------------------===//

#define DEBUG_TYPE "differentiation"

#include "swift/SILOptimizer/Differentiation/LinearMapInfo.h"
#include "swift/SILOptimizer/Differentiation/ADContext.h"

#include "swift/AST/DeclContext.h"
#include "swift/AST/ParameterList.h"
#include "swift/AST/SourceFile.h"
#include "swift/SIL/LoopInfo.h"

namespace swift {
namespace autodiff {

//===----------------------------------------------------------------------===//
// Local helpers
//===----------------------------------------------------------------------===//

/// Clone the generic parameters of the given generic signature and return a new
/// `GenericParamList`.
static GenericParamList *cloneGenericParameters(ASTContext &ctx,
                                                DeclContext *dc,
                                                CanGenericSignature sig) {
  SmallVector<GenericTypeParamDecl *, 2> clonedParams;
  for (auto paramType : sig.getGenericParams()) {
    auto clonedParam = new (ctx) GenericTypeParamDecl(
        dc, paramType->getName(), SourceLoc(), paramType->isTypeSequence(),
        paramType->getDepth(), paramType->getIndex());
    clonedParam->setDeclContext(dc);
    clonedParam->setImplicit(true);
    clonedParams.push_back(clonedParam);
  }
  return GenericParamList::create(ctx, SourceLoc(), clonedParams, SourceLoc());
}

//===----------------------------------------------------------------------===//
// LinearMapInfo methods
//===----------------------------------------------------------------------===//

LinearMapInfo::LinearMapInfo(ADContext &context, AutoDiffLinearMapKind kind,
                             SILFunction *original, SILFunction *derivative,
                             const AutoDiffConfig &config,
                             const DifferentiableActivityInfo &activityInfo,
                             SILLoopInfo *loopInfo)
    : kind(kind), original(original), derivative(derivative),
      activityInfo(activityInfo), loopInfo(loopInfo), config(config),
      synthesizedFile(context.getOrCreateSynthesizedFile(original)),
      typeConverter(context.getTypeConverter()) {
  generateDifferentiationDataStructures(context, derivative);
}

SILType LinearMapInfo::remapTypeInDerivative(SILType ty) {
  if (ty.hasArchetype())
    return derivative->mapTypeIntoContext(ty.mapTypeOutOfContext());
  return derivative->mapTypeIntoContext(ty);
}

VarDecl *LinearMapInfo::addVarDecl(NominalTypeDecl *nominal, StringRef name,
                                   Type type) {
  auto &astCtx = nominal->getASTContext();
  auto id = astCtx.getIdentifier(name);
  auto *varDecl = new (astCtx) VarDecl(
      /*IsStatic*/ false, VarDecl::Introducer::Var,
      SourceLoc(), id, nominal);
  varDecl->setAccess(AccessLevel::Private);
  if (type->hasArchetype())
    varDecl->setInterfaceType(type->mapTypeOutOfContext());
  else
    varDecl->setInterfaceType(type);
  nominal->addMember(varDecl);
  return varDecl;
}

void LinearMapInfo::computeAccessLevel(NominalTypeDecl *nominal,
                                       SILLinkage originalLinkage) {
  auto &astCtx = nominal->getASTContext();
  switch (originalLinkage) {
  case swift::SILLinkage::Public:
  case swift::SILLinkage::PublicNonABI:
    nominal->setAccess(AccessLevel::Internal);
    nominal->getAttrs().add(new (astCtx)
                                UsableFromInlineAttr(/*Implicit*/ true));
    break;
  case swift::SILLinkage::Hidden:
  case swift::SILLinkage::Shared:
    nominal->setAccess(AccessLevel::Internal);
    break;
  case swift::SILLinkage::Private:
    nominal->setAccess(AccessLevel::FilePrivate);
    break;
  default:
    // When the original function has external linkage, we create an internal
    // struct for use by our own module. This is necessary for cross-cell
    // differentiation in Jupyter.
    // TODO: Add a test in the compiler that exercises a similar situation as
    // cross-cell differentiation in Jupyter.
    nominal->setAccess(AccessLevel::Internal);
  }
}

EnumDecl *
LinearMapInfo::createBranchingTraceDecl(SILBasicBlock *originalBB,
                                        CanGenericSignature genericSig,
                                        SILLoopInfo *loopInfo) {
  assert(originalBB->getParent() == original);
  auto &astCtx = original->getASTContext();
  auto *moduleDecl = original->getModule().getSwiftModule();
  auto &file = getSynthesizedFile();
  // Create a branching trace enum.
  Mangle::ASTMangler mangler;
  auto config = this->config.withGenericSignature(genericSig);
  auto enumName = mangler.mangleAutoDiffGeneratedDeclaration(
      AutoDiffGeneratedDeclarationKind::BranchingTraceEnum,
      original->getName().str(), originalBB->getDebugID(), kind, config);
  auto enumId = astCtx.getIdentifier(enumName);
  auto loc = original->getLocation().getSourceLoc();
  GenericParamList *genericParams = nullptr;
  if (genericSig)
    genericParams = cloneGenericParameters(astCtx, &file, genericSig);
  auto *branchingTraceDecl = new (astCtx) EnumDecl(
      /*EnumLoc*/ SourceLoc(), /*Name*/ enumId, /*NameLoc*/ loc,
      /*Inherited*/ {}, /*GenericParams*/ genericParams, /*DC*/ &file);
  // Note: must mark enum as implicit to satisfy assertion in
  // `Parser::parseDeclListDelayed`.
  branchingTraceDecl->setImplicit();
  // Branching trace enums shall not be resilient.
  branchingTraceDecl->getAttrs().add(new (astCtx) FrozenAttr(/*implicit*/ true));
  if (genericSig)
    branchingTraceDecl->setGenericSignature(genericSig);
  computeAccessLevel(branchingTraceDecl, original->getEffectiveSymbolLinkage());
  file.addTopLevelDecl(branchingTraceDecl);
  // Add basic block enum cases.
  for (auto *predBB : originalBB->getPredecessorBlocks()) {
    // Create dummy declaration representing enum case parameter.
    auto *decl = new (astCtx)
        ParamDecl(loc, loc, Identifier(), loc, Identifier(), moduleDecl);
    decl->setSpecifier(ParamDecl::Specifier::Default);
    // If predecessor block is in a loop, its linear map struct will be
    // indirectly referenced in memory owned by the context object. The payload
    // is just a raw pointer.
    if (loopInfo->getLoopFor(predBB)) {
      blocksInLoop.insert(predBB);
      decl->setInterfaceType(astCtx.TheRawPointerType);
    }
    // Otherwise the payload is the linear map struct.
    else {
      auto *linearMapStruct = getLinearMapStruct(predBB);
      assert(linearMapStruct);
      auto linearMapStructTy =
          linearMapStruct->getDeclaredInterfaceType()->getCanonicalType();
      decl->setInterfaceType(
          linearMapStructTy->hasArchetype()
              ? linearMapStructTy->mapTypeOutOfContext() : linearMapStructTy);
    }
    // Create enum element and enum case declarations.
    auto *paramList = ParameterList::create(astCtx, {decl});
    auto bbId = "bb" + std::to_string(predBB->getDebugID());
    auto *enumEltDecl = new (astCtx) EnumElementDecl(
        /*IdentifierLoc*/ loc, DeclName(astCtx.getIdentifier(bbId)), paramList,
        loc, /*RawValueExpr*/ nullptr, branchingTraceDecl);
    enumEltDecl->setImplicit();
    auto *enumCaseDecl = EnumCaseDecl::create(
        /*CaseLoc*/ loc, {enumEltDecl}, branchingTraceDecl);
    enumCaseDecl->setImplicit();
    branchingTraceDecl->addMember(enumEltDecl);
    branchingTraceDecl->addMember(enumCaseDecl);
    // Record enum element declaration.
    branchingTraceEnumCases.insert({{predBB, originalBB}, enumEltDecl});
  }
  return branchingTraceDecl;
}

StructDecl *
LinearMapInfo::createLinearMapStruct(SILBasicBlock *originalBB,
                                     CanGenericSignature genericSig) {
  assert(originalBB->getParent() == original);
  auto *original = originalBB->getParent();
  auto &astCtx = original->getASTContext();
  auto &file = getSynthesizedFile();
  // Create a linear map struct.
  Mangle::ASTMangler mangler;
  auto config = this->config.withGenericSignature(genericSig);
  auto structName = mangler.mangleAutoDiffGeneratedDeclaration(
      AutoDiffGeneratedDeclarationKind::LinearMapStruct,
      original->getName().str(), originalBB->getDebugID(), kind, config);
  auto structId = astCtx.getIdentifier(structName);
  GenericParamList *genericParams = nullptr;
  if (genericSig)
    genericParams = cloneGenericParameters(astCtx, &file, genericSig);
  auto *linearMapStruct = new (astCtx) StructDecl(
      /*StructLoc*/ SourceLoc(), /*Name*/ structId, /*NameLoc*/ SourceLoc(),
      /*Inherited*/ {}, /*GenericParams*/ genericParams, /*DC*/ &file);
  // Note: must mark struct as implicit to satisfy assertion in
  // `Parser::parseDeclListDelayed`.
  linearMapStruct->setImplicit();
  // Linear map structs shall not be resilient.
  linearMapStruct->getAttrs().add(new (astCtx) FrozenAttr(/*implicit*/ true));
  if (genericSig)
    linearMapStruct->setGenericSignature(genericSig);
  computeAccessLevel(linearMapStruct, original->getEffectiveSymbolLinkage());
  file.addTopLevelDecl(linearMapStruct);
  return linearMapStruct;
}

VarDecl *LinearMapInfo::addLinearMapDecl(ApplyInst *ai, SILType linearMapType) {
  // IRGen requires decls to have AST types (not `SILFunctionType`), so we
  // convert the `SILFunctionType` of the linear map to a `FunctionType` with
  // the same parameters and results.
  auto silFnTy = linearMapType.castTo<SILFunctionType>();
  SmallVector<AnyFunctionType::Param, 8> params;
  for (auto &param : silFnTy->getParameters()) {
    ParameterTypeFlags flags;
    if (param.isIndirectMutating())
      flags = flags.withInOut(true);
    params.push_back(
        AnyFunctionType::Param(param.getInterfaceType(), Identifier(), flags));
  }

  AnyFunctionType *astFnTy;
  if (auto genSig = silFnTy->getSubstGenericSignature()) {
    // FIXME: Verify ExtInfo state is correct, not working by accident.
    GenericFunctionType::ExtInfo info;
    astFnTy = GenericFunctionType::get(
        genSig, params, silFnTy->getAllResultsInterfaceType().getASTType(),
        info);
  } else {
    FunctionType::ExtInfo info;
    astFnTy = FunctionType::get(
        params, silFnTy->getAllResultsInterfaceType().getASTType(), info);
  }

  auto *origBB = ai->getParent();
  auto *linMapStruct = getLinearMapStruct(origBB);
  std::string linearMapName;
  switch (kind) {
  case AutoDiffLinearMapKind::Differential:
    linearMapName = "differential_" + llvm::itostr(linearMapFieldMap.size());
    break;
  case AutoDiffLinearMapKind::Pullback:
    linearMapName = "pullback_" + llvm::itostr(linearMapFieldMap.size());
    break;
  }
  auto *linearMapDecl = addVarDecl(linMapStruct, linearMapName, astFnTy);
  linearMapFieldMap.insert({ai, linearMapDecl});
  return linearMapDecl;
}

void LinearMapInfo::addLinearMapToStruct(ADContext &context, ApplyInst *ai) {
  SmallVector<SILValue, 4> allResults;
  SmallVector<unsigned, 8> activeParamIndices;
  SmallVector<unsigned, 8> activeResultIndices;
  collectMinimalIndicesForFunctionCall(ai, config, activityInfo, allResults,
                                       activeParamIndices, activeResultIndices);

  // Check if there are any active results or arguments. If not, skip
  // this instruction.
  auto hasActiveResults = llvm::any_of(allResults, [&](SILValue res) {
    return activityInfo.isActive(res, config);
  });
  bool hasActiveInoutArgument = false;
  bool hasActiveArguments = false;
  auto numIndirectResults = ai->getNumIndirectResults();
  for (auto argIdx : range(ai->getSubstCalleeConv().getNumParameters())) {
    auto arg = ai->getArgumentsWithoutIndirectResults()[argIdx];
    if (activityInfo.isActive(arg, config)) {
      hasActiveArguments = true;
      auto paramInfo = ai->getSubstCalleeConv().getParamInfoForSILArg(
          numIndirectResults + argIdx);
      if (paramInfo.isIndirectMutating())
        hasActiveInoutArgument = true;
    }
  }
  if (!hasActiveArguments)
    return;
  if (!hasActiveResults && !hasActiveInoutArgument)
    return;

  // Compute differentiability parameters.
  // - If the callee has `@differentiable` function type, use differentiation
  //   parameters from the function type.
  // - Otherwise, use the active parameters.
  IndexSubset *parameters;
  auto origFnSubstTy = ai->getSubstCalleeType();
  auto remappedOrigFnSubstTy =
      remapTypeInDerivative(SILType::getPrimitiveObjectType(origFnSubstTy))
          .castTo<SILFunctionType>()
          ->getUnsubstitutedType(original->getModule());
  if (remappedOrigFnSubstTy->isDifferentiable()) {
    parameters = remappedOrigFnSubstTy->getDifferentiabilityParameterIndices();
  } else {
    parameters = IndexSubset::get(
        original->getASTContext(),
        ai->getArgumentsWithoutIndirectResults().size(), activeParamIndices);
  }
  // Compute differentiability results.
  auto numResults = remappedOrigFnSubstTy->getNumResults() +
                    remappedOrigFnSubstTy->getNumIndirectMutatingParameters();
  auto *results = IndexSubset::get(original->getASTContext(), numResults,
                                   activeResultIndices);
  // Create autodiff indices for the `apply` instruction.
  AutoDiffConfig applyConfig(parameters, results);

  // Check for non-differentiable original function type.
  auto checkNondifferentiableOriginalFunctionType = [&](CanSILFunctionType
                                                            origFnTy) {
    // Check non-differentiable arguments.
    for (auto paramIndex : applyConfig.parameterIndices->getIndices()) {
      auto remappedParamType =
          origFnTy->getParameters()[paramIndex].getSILStorageInterfaceType();
      if (!remappedParamType.isDifferentiable(derivative->getModule()))
        return true;
    }
    // Check non-differentiable results.
    for (auto resultIndex : applyConfig.resultIndices->getIndices()) {
      SILType remappedResultType;
      if (resultIndex >= origFnTy->getNumResults()) {
        auto inoutArgIdx = resultIndex - origFnTy->getNumResults();
        auto inoutArg =
            *std::next(ai->getInoutArguments().begin(), inoutArgIdx);
        remappedResultType = inoutArg->getType();
      } else {
        remappedResultType =
            origFnTy->getResults()[resultIndex].getSILStorageInterfaceType();
      }
      if (!remappedResultType.isDifferentiable(derivative->getModule()))
        return true;
    }
    return false;
  };
  if (checkNondifferentiableOriginalFunctionType(remappedOrigFnSubstTy))
    return;

  AutoDiffDerivativeFunctionKind derivativeFnKind(kind);
  auto derivativeFnType =
      remappedOrigFnSubstTy
          ->getAutoDiffDerivativeFunctionType(
              parameters, results, derivativeFnKind, context.getTypeConverter(),
              LookUpConformanceInModule(
                  derivative->getModule().getSwiftModule()))
          ->getUnsubstitutedType(original->getModule());

  auto derivativeFnResultTypes = derivativeFnType->getAllResultsInterfaceType();
  auto linearMapSILType = derivativeFnResultTypes;
  if (auto tupleType = linearMapSILType.getAs<TupleType>()) {
    linearMapSILType = SILType::getPrimitiveObjectType(
        tupleType.getElementType(tupleType->getElements().size() - 1));
  }
  if (auto fnTy = linearMapSILType.getAs<SILFunctionType>()) {
    linearMapSILType = SILType::getPrimitiveObjectType(
        fnTy->getUnsubstitutedType(original->getModule()));
  }
  addLinearMapDecl(ai, linearMapSILType);
}

void LinearMapInfo::generateDifferentiationDataStructures(
    ADContext &context, SILFunction *derivativeFn) {
  auto &astCtx = original->getASTContext();
  // Get the derivative function generic signature.
  CanGenericSignature derivativeFnGenSig = nullptr;
  if (auto *derivativeFnGenEnv = derivativeFn->getGenericEnvironment())
    derivativeFnGenSig =
        derivativeFnGenEnv->getGenericSignature().getCanonicalSignature();

  // Create linear map struct for each original block.
  for (auto &origBB : *original) {
    auto *linearMapStruct = createLinearMapStruct(&origBB, derivativeFnGenSig);
    linearMapStructs.insert({&origBB, linearMapStruct});
  }

  // Create branching trace enum for each original block and add it as a field
  // in the corresponding struct.
  StringRef traceEnumFieldName;
  switch (kind) {
  case AutoDiffLinearMapKind::Differential:
    traceEnumFieldName = "successor";
    break;
  case AutoDiffLinearMapKind::Pullback:
    traceEnumFieldName = "predecessor";
    break;
  }
  for (auto &origBB : *original) {
    auto *traceEnum =
        createBranchingTraceDecl(&origBB, derivativeFnGenSig, loopInfo);
    branchingTraceDecls.insert({&origBB, traceEnum});
    if (origBB.isEntry())
      continue;
    // Add branching trace enum field to corresponding linear map struct.
    auto *linearMapStruct = getLinearMapStruct(&origBB);
    auto *traceEnumField = addVarDecl(
        linearMapStruct, astCtx.getIdentifier(traceEnumFieldName).str(),
        traceEnum->getDeclaredInterfaceType());
    linearMapStructEnumFields.insert({linearMapStruct, traceEnumField});
  }

  // Do not add linear map fields for semantic member accessors, which have
  // special-case pullback generation. Linear map structs should be empty.
  if (isSemanticMemberAccessor(original))
    return;

  // Add linear map fields to the linear map structs.
  for (auto &origBB : *original) {
    for (auto &inst : origBB) {
      if (auto *ai = dyn_cast<ApplyInst>(&inst)) {
        // Add linear map field to struct for active `apply` instructions.
        // Skip array literal intrinsic applications since array literal
        // initialization is linear and handled separately.
        if (!shouldDifferentiateApplySite(ai) ||
            ArraySemanticsCall(ai, semantics::ARRAY_UNINITIALIZED_INTRINSIC))
          continue;
        if (ArraySemanticsCall(ai, semantics::ARRAY_FINALIZE_INTRINSIC))
          continue;
        LLVM_DEBUG(getADDebugStream()
                   << "Adding linear map struct field for " << *ai);
        addLinearMapToStruct(context, ai);
      }
    }
  }

  // Print generated linear map structs and branching trace enums.
  // These declarations do not show up with `-emit-sil` because they are
  // implicit. Instead, use `-Xllvm -debug-only=differentiation` to test
  // declarations with FileCheck.
  LLVM_DEBUG({
    auto &s = getADDebugStream();
    PrintOptions printOptions;
    printOptions.TypeDefinitions = true;
    printOptions.ExplodePatternBindingDecls = true;
    printOptions.SkipImplicit = false;
    s << "Generated linear map structs and branching trace enums for @"
      << original->getName() << ":\n";
    for (auto &origBB : *original) {
      auto *linearMapStruct = getLinearMapStruct(&origBB);
      linearMapStruct->print(s, printOptions);
      s << '\n';
    }
    for (auto &origBB : *original) {
      auto *traceEnum = getBranchingTraceDecl(&origBB);
      traceEnum->print(s, printOptions);
      s << '\n';
    }
  });
}

/// Returns a flag that indicates whether the `apply` instruction should be
/// differentiated, given the differentiation indices of the instruction's
/// parent function. Whether the `apply` should be differentiated is determined
/// sequentially from the following conditions:
/// 1. The instruction has an active `inout` argument.
/// 2. The instruction is a call to the array literal initialization intrinsic
///    ("array.uninitialized_intrinsic"), where the result is active and where
///    there is a `store` of an active value into the array's buffer.
/// 3. The instruction has both an active result (direct or indirect) and an
///    active argument.
bool LinearMapInfo::shouldDifferentiateApplySite(FullApplySite applySite) {
  // Function applications with an active inout argument should be
  // differentiated.
  for (auto inoutArg : applySite.getInoutArguments())
    if (activityInfo.isActive(inoutArg, config))
      return true;

  bool hasActiveDirectResults = false;
  forEachApplyDirectResult(applySite, [&](SILValue directResult) {
    hasActiveDirectResults |= activityInfo.isActive(directResult, config);
  });
  bool hasActiveIndirectResults =
      llvm::any_of(applySite.getIndirectSILResults(), [&](SILValue result) {
        return activityInfo.isActive(result, config);
      });
  bool hasActiveResults = hasActiveDirectResults || hasActiveIndirectResults;

  // TODO: Pattern match to make sure there is at least one `store` to the
  // array's active buffer.
  if (ArraySemanticsCall(applySite.getInstruction(),
                         semantics::ARRAY_UNINITIALIZED_INTRINSIC) &&
      hasActiveResults)
    return true;

  auto arguments = applySite.getArgumentsWithoutIndirectResults();
  bool hasActiveArguments = llvm::any_of(arguments, [&](SILValue arg) {
    return activityInfo.isActive(arg, config);
  });
  return hasActiveResults && hasActiveArguments;
}

/// Returns a flag indicating whether the instruction should be differentiated,
/// given the differentiation indices of the instruction's parent function.
/// Whether the instruction should be differentiated is determined sequentially
/// from any of the following conditions:
/// 1. The instruction is a full apply site and `shouldDifferentiateApplyInst`
///    returns true.
/// 2. The instruction has a source operand and a destination operand, both
///    being active.
/// 3. The instruction is an allocation instruction and has an active result.
/// 4. The instruction performs reference counting, lifetime ending, access
///    ending, or destroying on an active operand.
/// 5. The instruction creates an SSA copy of an active operand.
bool LinearMapInfo::shouldDifferentiateInstruction(SILInstruction *inst) {
  // A full apply site with an active argument and an active result (direct or
  // indirect) should be differentiated.
  if (FullApplySite::isa(inst))
    return shouldDifferentiateApplySite(FullApplySite(inst));
  // Anything with an active result and an active operand should be
  // differentiated.
  auto hasActiveOperands =
      llvm::any_of(inst->getAllOperands(), [&](Operand &op) {
        return activityInfo.isActive(op.get(), config);
      });
  auto hasActiveResults = llvm::any_of(inst->getResults(), [&](SILValue val) {
    return activityInfo.isActive(val, config);
  });
  if (hasActiveOperands && hasActiveResults)
    return true;
    // `store`-like instructions do not have an SSA result, but have two
    // operands that represent the source and the destination. We treat them as
    // the input and the output, respectively.
    // For `store`-like instructions whose destination is an element address
    // from an `array.uninitialized_intrinsic` application, return true if the
    // intrinsic application (representing the semantic destination) is active.
#define CHECK_INST_TYPE_ACTIVE_DEST(INST)                                      \
  if (auto *castInst = dyn_cast<INST##Inst>(inst))                             \
    return activityInfo.isActive(castInst->getDest(), config);
  CHECK_INST_TYPE_ACTIVE_DEST(Store)
  CHECK_INST_TYPE_ACTIVE_DEST(StoreBorrow)
  CHECK_INST_TYPE_ACTIVE_DEST(CopyAddr)
  CHECK_INST_TYPE_ACTIVE_DEST(UnconditionalCheckedCastAddr)
#undef CHECK_INST_TYPE_ACTIVE_DEST
  // Should differentiate any allocation instruction that has an active result.
  if ((isa<AllocationInst>(inst) && hasActiveResults))
    return true;
  if (hasActiveOperands) {
    // Should differentiate any instruction that performs reference counting,
    // lifetime ending, access ending, or destroying on an active operand.
    if (isa<RefCountingInst>(inst) || isa<EndAccessInst>(inst) ||
        isa<EndBorrowInst>(inst) || isa<DeallocationInst>(inst) ||
        isa<DestroyValueInst>(inst) || isa<DestroyAddrInst>(inst))
      return true;
  }
  return false;
}

} // end namespace autodiff
} // end namespace swift
