//===--- SerializeSILPass.cpp ---------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2017 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
//
//===----------------------------------------------------------------------===//

#define DEBUG_TYPE "serialize-sil"
#include "swift/Strings.h"
#include "swift/SIL/ApplySite.h"
#include "swift/SIL/SILCloner.h"
#include "swift/SIL/SILFunction.h"
#include "swift/SILOptimizer/PassManager/Passes.h"
#include "swift/SILOptimizer/PassManager/Transforms.h"
#include "swift/SILOptimizer/Utils/BasicBlockOptUtils.h"

using namespace swift;

namespace {
/// In place map opaque archetypes to their underlying type in a function.
/// This needs to happen when a function changes from serializable to not
/// serializable.
class MapOpaqueArchetypes : public SILCloner<MapOpaqueArchetypes> {
  using SuperTy = SILCloner<MapOpaqueArchetypes>;

  SILBasicBlock *origEntryBlock;
  SILBasicBlock *clonedEntryBlock;
public:
  friend class SILCloner<MapOpaqueArchetypes>;
  friend class SILCloner<MapOpaqueArchetypes>;
  friend class SILInstructionVisitor<MapOpaqueArchetypes>;

  MapOpaqueArchetypes(SILFunction &fun) : SuperTy(fun) {
    origEntryBlock = fun.getEntryBlock();
    clonedEntryBlock = fun.createBasicBlock();
  }

  bool shouldSubstOpaqueArchetypes() const { return true; }

  void replace();
};
} // namespace

void MapOpaqueArchetypes::replace() {
  // Map the function arguments.
  SmallVector<SILValue, 4> entryArgs;
  entryArgs.reserve(origEntryBlock->getArguments().size());
  for (auto &origArg : origEntryBlock->getArguments()) {
    SILType mappedType = remapType(origArg->getType());
    auto *NewArg = clonedEntryBlock->createFunctionArgument(
        mappedType, origArg->getDecl(), true);
    NewArg->copyFlags(cast<SILFunctionArgument>(origArg));
    entryArgs.push_back(NewArg);
  }

  getBuilder().setInsertionPoint(clonedEntryBlock);
  auto &fn = getBuilder().getFunction();
  cloneFunctionBody(&fn, clonedEntryBlock, entryArgs,
                    true /*replaceOriginalFunctionInPlace*/);
  // Insert the new entry block at the beginning.
  fn.moveBlockBefore(clonedEntryBlock, fn.begin());
  removeUnreachableBlocks(fn);
  // We know that this pass does not create infinite loops even if it
  // deletes basic blocks.
  fn.setNeedBreakInfiniteLoops(false);
  // De-serializing `unreachable` instructions does not create incomplete
  // lifetimes. We assume that the serialized SIL has no incomplete lifetimes.
  fn.setNeedCompleteLifetimes(false);
}

static bool opaqueArchetypeWouldChange(TypeExpansionContext context,
                                       CanType ty) {
  if (!ty->hasOpaqueArchetype())
    return false;

  return ty.findIf([=](Type type) -> bool {
    if (auto opaqueTy = type->getAs<OpaqueTypeArchetypeType>()) {
      auto opaque = opaqueTy->getDecl();
      auto module = context.getContext()->getParentModule();
      OpaqueSubstitutionKind subKind =
          ReplaceOpaqueTypesWithUnderlyingTypes::shouldPerformSubstitution(
              opaque, module, context.getResilienceExpansion());
      return subKind != OpaqueSubstitutionKind::DontSubstitute;
    }
    return false;
  });
}

static bool hasOpaqueArchetypeOperand(TypeExpansionContext context,
                                      SILInstruction &inst) {
  // Check the operands for opaque types.
  for (auto &opd : inst.getAllOperands())
    if (opaqueArchetypeWouldChange(context, opd.get()->getType().getASTType()))
      return true;
  return false;
}

static bool hasOpaqueArchetypeResult(TypeExpansionContext context,
                                     SILInstruction &inst) {
  // Check the results for opaque types.
  for (SILValue res : inst.getResults())
    if (opaqueArchetypeWouldChange(context, res->getType().getASTType()))
      return true;
  return false;
}

static bool hasOpaqueArchetype(TypeExpansionContext context,
                               SILInstruction &inst) {
  // Check operands and results.
  if (hasOpaqueArchetypeOperand(context, inst))
    return true;
  if (hasOpaqueArchetypeResult(context, inst))
    return true;

  // Check substitution maps.
  switch (inst.getKind()) {
  case SILInstructionKind::AllocStackInst:
  case SILInstructionKind::AllocPackInst:
  case SILInstructionKind::AllocPackMetadataInst:
  case SILInstructionKind::AllocRefInst:
  case SILInstructionKind::AllocRefDynamicInst:
  case SILInstructionKind::AllocBoxInst:
  case SILInstructionKind::AllocExistentialBoxInst:
  case SILInstructionKind::IndexAddrInst:
  case SILInstructionKind::TailAddrInst:
  case SILInstructionKind::IndexRawPointerInst:
  case SILInstructionKind::FunctionRefInst:
  case SILInstructionKind::DynamicFunctionRefInst:
  case SILInstructionKind::PreviousDynamicFunctionRefInst:
  case SILInstructionKind::GlobalAddrInst:
  case SILInstructionKind::GlobalValueInst:
  case SILInstructionKind::BaseAddrForOffsetInst:
  case SILInstructionKind::IntegerLiteralInst:
  case SILInstructionKind::FloatLiteralInst:
  case SILInstructionKind::StringLiteralInst:
  case SILInstructionKind::ClassMethodInst:
  case SILInstructionKind::SuperMethodInst:
  case SILInstructionKind::ObjCMethodInst:
  case SILInstructionKind::ObjCSuperMethodInst:
  case SILInstructionKind::WitnessMethodInst:
  case SILInstructionKind::UpcastInst:
  case SILInstructionKind::AddressToPointerInst:
  case SILInstructionKind::PointerToAddressInst:
  case SILInstructionKind::UncheckedRefCastInst:
  case SILInstructionKind::UncheckedAddrCastInst:
  case SILInstructionKind::UncheckedTrivialBitCastInst:
  case SILInstructionKind::UncheckedBitwiseCastInst:
  case SILInstructionKind::UncheckedValueCastInst:
  case SILInstructionKind::RefToRawPointerInst:
  case SILInstructionKind::RawPointerToRefInst:
#define LOADABLE_REF_STORAGE(Name, ...)                                        \
  case SILInstructionKind::RefTo##Name##Inst:                                  \
  case SILInstructionKind::Name##ToRefInst:
#include "swift/AST/ReferenceStorage.def"
#undef LOADABLE_REF_STORAGE_HELPER
  case SILInstructionKind::ConvertFunctionInst:
  case SILInstructionKind::ConvertEscapeToNoEscapeInst:
  case SILInstructionKind::RefToBridgeObjectInst:
  case SILInstructionKind::BridgeObjectToRefInst:
  case SILInstructionKind::BridgeObjectToWordInst:
  case SILInstructionKind::ThinToThickFunctionInst:
  case SILInstructionKind::ThickToObjCMetatypeInst:
  case SILInstructionKind::ObjCToThickMetatypeInst:
  case SILInstructionKind::ObjCMetatypeToObjectInst:
  case SILInstructionKind::ObjCExistentialMetatypeToObjectInst:
  case SILInstructionKind::UnconditionalCheckedCastInst:
  case SILInstructionKind::ClassifyBridgeObjectInst:
  case SILInstructionKind::ValueToBridgeObjectInst:
  case SILInstructionKind::MarkDependenceInst:
  case SILInstructionKind::MarkDependenceAddrInst:
  case SILInstructionKind::MergeIsolationRegionInst:
  case SILInstructionKind::CopyBlockInst:
  case SILInstructionKind::CopyBlockWithoutEscapingInst:
  case SILInstructionKind::CopyValueInst:
  case SILInstructionKind::ExplicitCopyValueInst:
  case SILInstructionKind::MoveValueInst:
  case SILInstructionKind::DropDeinitInst:
  case SILInstructionKind::MarkUnresolvedNonCopyableValueInst:
  case SILInstructionKind::MarkUnresolvedReferenceBindingInst:
  case SILInstructionKind::CopyableToMoveOnlyWrapperValueInst:
  case SILInstructionKind::MoveOnlyWrapperToCopyableValueInst:
  case SILInstructionKind::UnownedCopyValueInst:
  case SILInstructionKind::WeakCopyValueInst:
#define REF_STORAGE(Name, ...)                                                 \
  case SILInstructionKind::StrongCopy##Name##ValueInst:
#include "swift/AST/ReferenceStorage.def"
  case SILInstructionKind::UncheckedOwnershipConversionInst:
  case SILInstructionKind::IsUniqueInst:
  case SILInstructionKind::DestroyNotEscapedClosureInst:
  case SILInstructionKind::LoadInst:
  case SILInstructionKind::LoadBorrowInst:
  case SILInstructionKind::BeginBorrowInst:
  case SILInstructionKind::BorrowedFromInst:
  case SILInstructionKind::StoreBorrowInst:
  case SILInstructionKind::BeginAccessInst:
  case SILInstructionKind::MoveOnlyWrapperToCopyableAddrInst:
  case SILInstructionKind::MoveOnlyWrapperToCopyableBoxInst:
  case SILInstructionKind::CopyableToMoveOnlyWrapperAddrInst:
#define NEVER_OR_SOMETIMES_LOADABLE_CHECKED_REF_STORAGE(Name, name, ...)       \
  case SILInstructionKind::Load##Name##Inst:
#include "swift/AST/ReferenceStorage.def"
#undef NEVER_OR_SOMETIMES_LOADABLE_CHECKED_REF_STORAGE
  case SILInstructionKind::MarkUninitializedInst:
  case SILInstructionKind::ProjectBoxInst:
  case SILInstructionKind::ProjectExistentialBoxInst:
  case SILInstructionKind::BuiltinInst:
  case SILInstructionKind::MetatypeInst:
  case SILInstructionKind::ValueMetatypeInst:
  case SILInstructionKind::ExistentialMetatypeInst:
  case SILInstructionKind::ObjCProtocolInst:
  case SILInstructionKind::ObjectInst:
  case SILInstructionKind::VectorInst:
  case SILInstructionKind::VectorBaseAddrInst:
  case SILInstructionKind::TupleInst:
  case SILInstructionKind::TupleAddrConstructorInst:
  case SILInstructionKind::TupleExtractInst:
  case SILInstructionKind::TuplePackExtractInst:
  case SILInstructionKind::TupleElementAddrInst:
  case SILInstructionKind::StructInst:
  case SILInstructionKind::StructExtractInst:
  case SILInstructionKind::StructElementAddrInst:
  case SILInstructionKind::RefElementAddrInst:
  case SILInstructionKind::RefTailAddrInst:
  case SILInstructionKind::EnumInst:
  case SILInstructionKind::UncheckedEnumDataInst:
  case SILInstructionKind::InitEnumDataAddrInst:
  case SILInstructionKind::UncheckedTakeEnumDataAddrInst:
  case SILInstructionKind::UncheckedBorrowEnumDataAddrInst:
  case SILInstructionKind::UncheckedInPlaceEnumDataAddrInst:
  case SILInstructionKind::SelectEnumInst:
  case SILInstructionKind::SelectEnumAddrInst:
  case SILInstructionKind::InitExistentialAddrInst:
  case SILInstructionKind::InitExistentialValueInst:
  case SILInstructionKind::OpenExistentialAddrInst:
  case SILInstructionKind::InitExistentialRefInst:
  case SILInstructionKind::OpenExistentialRefInst:
  case SILInstructionKind::InitExistentialMetatypeInst:
  case SILInstructionKind::OpenExistentialMetatypeInst:
  case SILInstructionKind::OpenExistentialBoxInst:
  case SILInstructionKind::OpenExistentialValueInst:
  case SILInstructionKind::OpenExistentialBoxValueInst:
  case SILInstructionKind::ProjectBlockStorageInst:
  case SILInstructionKind::InitBlockStorageHeaderInst:
  case SILInstructionKind::KeyPathInst:
  case SILInstructionKind::UnreachableInst:
  case SILInstructionKind::ReturnInst:
  case SILInstructionKind::ReturnBorrowInst:
  case SILInstructionKind::ThrowInst:
  case SILInstructionKind::ThrowAddrInst:
  case SILInstructionKind::YieldInst:
  case SILInstructionKind::UnwindInst:
  case SILInstructionKind::BranchInst:
  case SILInstructionKind::CondBranchInst:
  case SILInstructionKind::SwitchValueInst:
  case SILInstructionKind::SwitchEnumInst:
  case SILInstructionKind::SwitchEnumAddrInst:
  case SILInstructionKind::DynamicMethodBranchInst:
  case SILInstructionKind::CheckedCastBranchInst:
  case SILInstructionKind::CheckedCastAddrBranchInst:
  case SILInstructionKind::DeallocStackInst:
  case SILInstructionKind::DeallocStackRefInst:
  case SILInstructionKind::DeallocPackInst:
  case SILInstructionKind::DeallocPackMetadataInst:
  case SILInstructionKind::DeallocRefInst:
  case SILInstructionKind::DeallocPartialRefInst:
  case SILInstructionKind::DeallocBoxInst:
  case SILInstructionKind::DeallocExistentialBoxInst:
  case SILInstructionKind::StrongRetainInst:
  case SILInstructionKind::StrongReleaseInst:
  case SILInstructionKind::UnmanagedRetainValueInst:
  case SILInstructionKind::UnmanagedReleaseValueInst:
  case SILInstructionKind::UnmanagedAutoreleaseValueInst:
#define ALWAYS_OR_SOMETIMES_LOADABLE_CHECKED_REF_STORAGE(Name, ...)            \
  case SILInstructionKind::StrongRetain##Name##Inst:                           \
  case SILInstructionKind::Name##RetainInst:                                   \
  case SILInstructionKind::Name##ReleaseInst:
#include "swift/AST/ReferenceStorage.def"
#undef ALWAYS_OR_SOMETIMES_LOADABLE_CHECKED_REF_STORAGE
  case SILInstructionKind::RetainValueInst:
  case SILInstructionKind::RetainValueAddrInst:
  case SILInstructionKind::ReleaseValueInst:
  case SILInstructionKind::ReleaseValueAddrInst:
  case SILInstructionKind::BeginDeallocRefInst:
  case SILInstructionKind::EndInitLetRefInst:
  case SILInstructionKind::AutoreleaseValueInst:
  case SILInstructionKind::BindMemoryInst:
  case SILInstructionKind::RebindMemoryInst:
  case SILInstructionKind::FixLifetimeInst:
  case SILInstructionKind::DestroyValueInst:
  case SILInstructionKind::EndBorrowInst:
  case SILInstructionKind::EndAccessInst:
  case SILInstructionKind::BeginUnpairedAccessInst:
  case SILInstructionKind::EndUnpairedAccessInst:
  case SILInstructionKind::StoreInst:
  case SILInstructionKind::AssignInst:
  case SILInstructionKind::AssignOrInitInst:
  case SILInstructionKind::MarkFunctionEscapeInst:
  case SILInstructionKind::DebugValueInst:
  case SILInstructionKind::DebugStepInst:
  case SILInstructionKind::SpecifyTestInst:
#define NEVER_OR_SOMETIMES_LOADABLE_CHECKED_REF_STORAGE(Name, ...)             \
  case SILInstructionKind::Store##Name##Inst:
#include "swift/AST/ReferenceStorage.def"
  case SILInstructionKind::CopyAddrInst:
  case SILInstructionKind::ExplicitCopyAddrInst:
  case SILInstructionKind::MarkUnresolvedMoveAddrInst:
  case SILInstructionKind::DestroyAddrInst:
  case SILInstructionKind::EndLifetimeInst:
  case SILInstructionKind::ExtendLifetimeInst:
  case SILInstructionKind::InjectEnumAddrInst:
  case SILInstructionKind::DeinitExistentialAddrInst:
  case SILInstructionKind::DeinitExistentialValueInst:
  case SILInstructionKind::UnconditionalCheckedCastAddrInst:
  case SILInstructionKind::UncheckedRefCastAddrInst:
  case SILInstructionKind::AllocGlobalInst:
  case SILInstructionKind::EndApplyInst:
  case SILInstructionKind::AbortApplyInst:
  case SILInstructionKind::CondFailInst:
  case SILInstructionKind::DestructureStructInst:
  case SILInstructionKind::DestructureTupleInst:
  case SILInstructionKind::DifferentiableFunctionInst:
  case SILInstructionKind::DifferentiableFunctionExtractInst:
  case SILInstructionKind::LinearFunctionInst:
  case SILInstructionKind::LinearFunctionExtractInst:
  case SILInstructionKind::DifferentiabilityWitnessFunctionInst:
  case SILInstructionKind::BeginCOWMutationInst:
  case SILInstructionKind::EndCOWMutationInst:
  case SILInstructionKind::EndCOWMutationAddrInst:
  case SILInstructionKind::IncrementProfilerCounterInst:
  case SILInstructionKind::GetAsyncContinuationInst:
  case SILInstructionKind::GetAsyncContinuationAddrInst:
  case SILInstructionKind::AwaitAsyncContinuationInst:
  case SILInstructionKind::HopToExecutorInst:
  case SILInstructionKind::ExtractExecutorInst:
  case SILInstructionKind::FunctionExtractIsolationInst:
  case SILInstructionKind::HasSymbolInst:
  case SILInstructionKind::PackElementGetInst:
  case SILInstructionKind::PackElementSetInst:
  case SILInstructionKind::TuplePackElementAddrInst:
  case SILInstructionKind::TypeValueInst:
  case SILInstructionKind::IgnoredUseInst:
  case SILInstructionKind::ImplicitActorToOpaqueIsolationCastInst:
  case SILInstructionKind::UncheckedOwnershipInst:
  case SILInstructionKind::MakeBorrowInst:
  case SILInstructionKind::DereferenceBorrowInst:
  case SILInstructionKind::MakeAddrBorrowInst:
  case SILInstructionKind::DereferenceAddrBorrowInst:
  case SILInstructionKind::InitBorrowAddrInst:
  case SILInstructionKind::DereferenceBorrowAddrInst:
    // Handle by operand and result check.
    break;

  case SILInstructionKind::PackLengthInst:
    // We reduce the pack shape, so it would very odd if this pack had
    // opaque archetypes in it, but there's no harm in doing it right.
    return opaqueArchetypeWouldChange(context,
              cast<PackLengthInst>(&inst)->getPackType());

  case SILInstructionKind::DynamicPackIndexInst:
  case SILInstructionKind::PackPackIndexInst:
  case SILInstructionKind::ScalarPackIndexInst:
    return opaqueArchetypeWouldChange(context,
              cast<AnyPackIndexInst>(&inst)->getIndexedPackType());

  case SILInstructionKind::OpenPackElementInst: {
    auto open = cast<OpenPackElementInst>(&inst);
    bool wouldChange = false;
    open->getOpenedGenericEnvironment()
        ->forEachPackElementBinding([&](ElementArchetypeType *elementType,
                                        PackType *packSubstitution) {
      if (!wouldChange) {
        if (opaqueArchetypeWouldChange(context,
                                       packSubstitution->getCanonicalType()))
          wouldChange = true;
      }
    });
    return wouldChange;
  }

  case SILInstructionKind::ThunkInst: {
    auto subs = cast<ThunkInst>(&inst)->getSubstitutionMap();
    for (auto ty : subs.getReplacementTypes()) {
      if (opaqueArchetypeWouldChange(context, ty->getCanonicalType()))
        return true;
    }
    break;
  }

  case SILInstructionKind::ApplyInst:
  case SILInstructionKind::PartialApplyInst:
  case SILInstructionKind::TryApplyInst:
  case SILInstructionKind::BeginApplyInst:
    // Check substitution map.
    auto apply = ApplySite(&inst);
    auto subs = apply.getSubstitutionMap();
    for (auto ty: subs.getReplacementTypes()) {
       if (opaqueArchetypeWouldChange(context, ty->getCanonicalType()))
         return true;
    }
    break;
  }

  return false;
}

static bool hasOpaqueArchetypeArgument(TypeExpansionContext context, SILBasicBlock &BB) {
  for (auto *arg : BB.getArguments()) {
    if (opaqueArchetypeWouldChange(context, arg->getType().getASTType()))
      return true;
  }
  return false;
}

static bool hasAnyOpaqueArchetype(SILFunction &F) {
  bool foundOpaqueArchetype = false;
  auto context = F.getTypeExpansionContext();
  for (auto &BB : F) {
    // Check basic block argument types.
    if (hasOpaqueArchetypeArgument(context, BB)) {
      foundOpaqueArchetype = true;
      break;
    }

    // Check instruction results and operands.
    for (auto &inst : BB) {
      if (hasOpaqueArchetype(context, inst)) {
        foundOpaqueArchetype = true;
        break;
      }
    }

    if (foundOpaqueArchetype)
      break;
  }

  return foundOpaqueArchetype;
}

void updateOpaqueArchetypes(SILFunction &F) {
  // Only map if there are opaque archetypes that could change.
  if (!hasAnyOpaqueArchetype(F))
    return;

  MapOpaqueArchetypes(F).replace();
}

/// A utility pass to serialize a SILModule at any place inside the optimization
/// pipeline.
class SerializeSILPass : public SILModuleTransform {
    
  /// Removes [serialized] from all functions. This allows for more
  /// optimizations and for a better dead function elimination.
  void removeSerializedFlagFromAllFunctions(SILModule &M) {
    for (auto &F : M) {
      bool wasSerialized = F.isAnySerialized();
      F.setSerializedKind(IsNotSerialized);

      // We are removing [serialized] from the function. This will change how
      // opaque archetypes are lowered in SIL - they might lower to their
      // underlying type. Update the function's opaque archetypes.
      if (wasSerialized && F.isDefinition()) {
        updateOpaqueArchetypes(F);
        invalidateAnalysis(&F, SILAnalysis::InvalidationKind::FunctionBody);
      }

      // After serialization we don't need to keep @alwaysEmitIntoClient
      // functions alive, i.e. we don't need to treat them as public functions.
      if (M.isWholeModule()) {
        switch (F.getLinkage()) {
        case SILLinkage::PublicNonABI:
        case SILLinkage::PackageNonABI:
          F.setLinkage(SILLinkage::Shared);
          break;
        case SILLinkage::Public:
        case SILLinkage::Package:
        case SILLinkage::Hidden:
        case SILLinkage::Shared:
        case SILLinkage::Private:
        case SILLinkage::PublicExternal:
        case SILLinkage::PackageExternal:
        case SILLinkage::HiddenExternal:
          break;
        }
      }
    }

    for (auto &WT : M.getWitnessTables()) {
      WT.setSerializedKind(IsNotSerialized);
    }

    for (auto &VT : M.getVTables()) {
      VT->setSerializedKind(IsNotSerialized);
    }

    for (auto &Deinit : M.getMoveOnlyDeinits()) {
      Deinit->setSerializedKind(IsNotSerialized);
    }
  }

public:
  SerializeSILPass() {}
  
  void run() override {
    auto &M = *getModule();
    // Nothing to do if the module was serialized already.
    if (M.isSerialized())
      return;
    
    LLVM_DEBUG(llvm::dbgs() << "Serializing SILModule in SerializeSILPass\n");
    M.serialize();

    removeSerializedFlagFromAllFunctions(M);
  }
};

SILTransform *swift::createSerializeSILPass() {
  return new SerializeSILPass();
}
