//===--- LoadableByAddress.cpp - Lower SIL address-only types. ------------===//
//
// 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
//
// This pass lowers loadable SILTypes. On completion, the SILType of every
// function argument is an address instead of the type itself.
// This reduces the code size.
// Consequently, this pass is required for IRGen.
// It is a mandatory IRGen preparation pass (not a diagnostic pass).
//
//===----------------------------------------------------------------------===//

#include "swift/AST/Types.h"
#include "swift/SIL/Dominance.h"
#include "swift/SIL/SILInstruction.h"
#define DEBUG_TYPE "loadable-address"
#include "Explosion.h"
#include "FixedTypeInfo.h"
#include "IRGenMangler.h"
#include "IRGenModule.h"
#include "NativeConventionSchema.h"
#include "swift/AST/GenericEnvironment.h"
#include "swift/Basic/Assertions.h"
#include "swift/IRGen/IRGenSILPasses.h"
#include "swift/SIL/DebugUtils.h"
#include "swift/SIL/PrettyStackTrace.h"
#include "swift/SIL/SILArgument.h"
#include "swift/SIL/SILBuilder.h"
#include "swift/SIL/SILCloner.h"
#include "swift/SIL/SILUndef.h"
#include "swift/SILOptimizer/Analysis/AliasAnalysis.h"
#include "swift/SILOptimizer/Analysis/DeadEndBlocksAnalysis.h"
#include "swift/SILOptimizer/PassManager/Transforms.h"
#include "swift/SILOptimizer/Utils/BasicBlockOptUtils.h"
#include "swift/SILOptimizer/Utils/CompileTimeInterpolationUtils.h"
#include "swift/SILOptimizer/Utils/DebugOptUtils.h"
#include "swift/SILOptimizer/Utils/InstOptUtils.h"
#include "swift/SILOptimizer/Utils/StackNesting.h"
#include "swift/SILOptimizer/Utils/ValueLifetime.h"
#include "llvm/ADT/MapVector.h"
#include "llvm/ADT/SetVector.h"
#include "llvm/ADT/SmallSet.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/Debug.h"

using namespace swift;
using namespace swift::irgen;

static GenericEnvironment *getSubstGenericEnvironment(CanSILFunctionType fnTy) {
  return fnTy->getSubstGenericSignature().getGenericEnvironment();
}

static GenericEnvironment *getSubstGenericEnvironment(SILFunction *F) {
  if (F->getLoweredFunctionType()->isPolymorphic())
    return F->getGenericEnvironment();
  return getSubstGenericEnvironment(F->getLoweredFunctionType());
}

class LargeSILTypeMapper {
public:
  LargeSILTypeMapper() {}

public:
  SILType getNewSILType(GenericEnvironment *GenericEnv, SILType storageType,
                        irgen::IRGenModule &Mod);
  bool shouldTransformResults(GenericEnvironment *env,
                              CanSILFunctionType fnType,
                              irgen::IRGenModule &IGM);
  bool shouldTransformFunctionType(GenericEnvironment *env,
                                   CanSILFunctionType fnType,
                                   irgen::IRGenModule &IGM);
  SILParameterInfo getNewParameter(GenericEnvironment *env,
                                   SILParameterInfo param,
                                   irgen::IRGenModule &IGM);
  bool shouldTransformParameter(GenericEnvironment *env, SILParameterInfo param,
                                irgen::IRGenModule &IGM);
  SmallVector<SILParameterInfo, 4> getNewParameters(GenericEnvironment *env,
                                                    CanSILFunctionType fnType,
                                                    irgen::IRGenModule &IGM);
  SmallVector<SILYieldInfo, 2> getNewYields(GenericEnvironment *env,
                                            CanSILFunctionType fnType,
                                            irgen::IRGenModule &IGM);
  SmallVector<SILResultInfo, 2> getNewResults(GenericEnvironment *GenericEnv,
                                              CanSILFunctionType fnType,
                                              irgen::IRGenModule &Mod,
                                              bool mustTransform = false);
  CanSILFunctionType getNewSILFunctionType(GenericEnvironment *env,
                                           CanSILFunctionType fnType,
                                           irgen::IRGenModule &IGM,
                                           bool mustTransform = false);
  SILType getNewOptionalFunctionType(GenericEnvironment *GenericEnv,
                                     SILType storageType,
                                     irgen::IRGenModule &Mod);
  SILType getNewTupleType(GenericEnvironment *GenericEnv,
                          irgen::IRGenModule &Mod,
                          const SILType &nonOptionalType,
                          const SILType &storageType);
  bool newResultsDiffer(GenericEnvironment *GenericEnv,
                        ArrayRef<SILResultInfo> origResults,
                        irgen::IRGenModule &Mod);
  bool shouldConvertBBArg(SILArgument *arg, irgen::IRGenModule &Mod);
  bool containsDifferentFunctionSignature(GenericEnvironment *genEnv,
                                          irgen::IRGenModule &Mod,
                                          SILType storageType,
                                          SILType newSILType);

private:
  // Cache of already computed type transforms
  llvm::MapVector<std::pair<GenericEnvironment *, SILType>, SILType>
      oldToNewTypeMap;
};

/// Utility to determine if this is a large loadable type
static bool isLargeLoadableType(GenericEnvironment *GenericEnv, SILType t,
                                irgen::IRGenModule &Mod) {
  if (t.isAddress() || t.isClassOrClassMetatype()) {
    return false;
  }

  auto canType = t.getASTType();
  if (canType->hasTypeParameter()) {
    assert(GenericEnv && "Expected a GenericEnv");
    canType = GenericEnv->mapTypeIntoEnvironment(canType)->getCanonicalType();
  }

  if (canType.getAnyGeneric() || t.is<BuiltinFixedArrayType>()) {
    assert(t.isObject() && "Expected only two categories: address and object");
    assert(!canType->hasTypeParameter());
    const TypeInfo &TI = Mod.getTypeInfoForLowered(canType);
    auto &nativeSchemaOrigParam = TI.nativeParameterValueSchema(Mod);
    return nativeSchemaOrigParam.requiresIndirect();
  }
  return false;
}

/// Check if a type is a large loadable tuple type that requires indirect
/// passing according to the native calling convention.
static bool isLargeLoadableTupleType(GenericEnvironment *GenericEnv, SILType t,
                                     irgen::IRGenModule &Mod) {
  if (t.isAddress() || t.isClassOrClassMetatype()) {
    return false;
  }

  auto canType = t.getASTType();
  if (canType->hasTypeParameter()) {
    assert(GenericEnv && "Expected a GenericEnv");
    canType = GenericEnv->mapTypeIntoEnvironment(canType)->getCanonicalType();
  }

  if (isa<TupleType>(canType)) {
    assert(t.isObject() && "Expected only two categories: address and object");
    assert(!canType->hasTypeParameter());
    const TypeInfo &TI = Mod.getTypeInfoForLowered(canType);
    auto &nativeSchemaOrigParam = TI.nativeParameterValueSchema(Mod);
    return nativeSchemaOrigParam.requiresIndirect();
  }
  return false;
}

static bool modifiableFunction(CanSILFunctionType funcType) {
  if (funcType->getLanguage() == SILFunctionLanguage::C) {
    // C functions should use the old ABI
    return false;
  }
  return true;
}

bool LargeSILTypeMapper::shouldTransformParameter(GenericEnvironment *env,
                                                  SILParameterInfo param,
                                                  irgen::IRGenModule &IGM) {
  auto newParam = getNewParameter(env, param, IGM);
  return (param != newParam);
}

static bool isFuncOrOptionalFuncType(SILType Ty) {
  SILType nonOptionalType = Ty;
  if (auto optType = Ty.getOptionalObjectType()) {
    nonOptionalType = optType;
  }
  return nonOptionalType.is<SILFunctionType>();
}

bool LargeSILTypeMapper::shouldTransformFunctionType(GenericEnvironment *env,
                                                     CanSILFunctionType fnType,
                                                     irgen::IRGenModule &IGM) {
  // Map substituted function types according to their substituted generic
  // signature.
  if (fnType->getPatternSubstitutions()) {
    env = getSubstGenericEnvironment(fnType);
  }

  if (shouldTransformResults(env, fnType, IGM))
    return true;

  for (auto param : fnType->getParameters()) {
    if (shouldTransformParameter(env, param, IGM))
      return true;
  }

  for (auto yield : fnType->getYields()) {
    if (shouldTransformParameter(env, yield, IGM))
      return true;
  }

  return false;
}

// Get the function type or the optional function type
static CanSILFunctionType getInnerFunctionType(SILType storageType) {
  if (auto currSILFunctionType = storageType.getAs<SILFunctionType>()) {
    return currSILFunctionType;
  }
  if (auto optionalType = storageType.getOptionalObjectType()) {
    if (auto currSILFunctionType = optionalType.getAs<SILFunctionType>()) {
      return currSILFunctionType;
    }
  }
  return CanSILFunctionType();
}

static SILType getNonOptionalType(SILType t) {
  SILType nonOptionalType = t;
  if (auto optType = t.getOptionalObjectType()) {
    nonOptionalType = optType;
  }
  return nonOptionalType;
}

bool LargeSILTypeMapper::containsDifferentFunctionSignature(
    GenericEnvironment *genEnv, irgen::IRGenModule &Mod, SILType storageType,
    SILType newSILType) {
  if (storageType == newSILType) {
    return false;
  }
  if (getInnerFunctionType(storageType)) {
    return true;
  }
  SILType nonOptionalType = getNonOptionalType(storageType);
  if (nonOptionalType.getAs<TupleType>()) {
    auto origType = nonOptionalType.getAs<TupleType>();
    for (TupleTypeElt canElem : origType->getElements()) {
      auto origCanType = CanType(canElem.getType());
      auto elem = SILType::getPrimitiveObjectType(origCanType);
      auto newElem = getNewSILType(genEnv, elem, Mod);
      if (containsDifferentFunctionSignature(genEnv, Mod, elem, newElem)) {
        return true;
      }
    }
  }
  return false;
}

bool LargeSILTypeMapper::newResultsDiffer(GenericEnvironment *GenericEnv,
                                          ArrayRef<SILResultInfo> origResults,
                                          irgen::IRGenModule &Mod) {
  SmallVector<SILResultInfo, 2> newResults;
  for (auto result : origResults) {
    SILType currResultTy = result.getSILStorageInterfaceType();
    SILType newSILType = getNewSILType(GenericEnv, currResultTy, Mod);
    // We (currently) only care about function signatures
    if (containsDifferentFunctionSignature(GenericEnv, Mod, currResultTy,
                                           newSILType)) {
      return true;
    }
  }
  return false;
}

static bool modNonFuncTypeResultType(GenericEnvironment *genEnv,
                                     CanSILFunctionType loweredTy,
                                     irgen::IRGenModule &Mod,
                                     bool mustTransform = false) {
  if (!modifiableFunction(loweredTy) && !mustTransform) {
    return false;
  }
  if (loweredTy->getNumResults() != 1) {
    return false;
  }
  auto singleResult = loweredTy->getSingleResult();
  auto resultStorageType = singleResult.getSILStorageInterfaceType();
  if (isLargeLoadableType(genEnv, resultStorageType, Mod)) {
    return true;
  }
  // For borrow accessors (guaranteed results), also handle large tuple types.
  // We can't transform large loadable tuples for other functions, because of
  // ABI differences.
  if (singleResult.getConvention() == ResultConvention::Guaranteed &&
      isLargeLoadableTupleType(genEnv, resultStorageType, Mod)) {
    return true;
  }
  return false;
}

SmallVector<SILResultInfo, 2>
LargeSILTypeMapper::getNewResults(GenericEnvironment *GenericEnv,
                                  CanSILFunctionType fnType,
                                  irgen::IRGenModule &Mod,
                                  bool mustTransform) {
  // Get new SIL Function results - same as old results UNLESS:
  // 1) Function type results might have a different signature
  // 2) Large loadables are replaced by @out version
  auto origResults = fnType->getResults();
  SmallVector<SILResultInfo, 2> newResults;
  for (auto result : origResults) {
    SILType currResultTy = result.getSILStorageInterfaceType();
    SILType newSILType = getNewSILType(GenericEnv, currResultTy, Mod);
    if (modNonFuncTypeResultType(GenericEnv, fnType, Mod, mustTransform)) {
      // Case (2) Above
      SILResultInfo newSILResultInfo(newSILType.getASTType(),
                                     result.getConvention() ==
                                             ResultConvention::Guaranteed
                                         ? ResultConvention::GuaranteedAddress
                                         : ResultConvention::Indirect);
      newResults.push_back(newSILResultInfo);
    } else if (containsDifferentFunctionSignature(GenericEnv, Mod, currResultTy,
                                                  newSILType)) {
      // Case (1) Above
      SILResultInfo newResult(newSILType.getASTType(), result.getConvention());
      newResults.push_back(newResult);
    } else {
      newResults.push_back(result);
    }
  }
  return newResults;
}

CanSILFunctionType
LargeSILTypeMapper::getNewSILFunctionType(GenericEnvironment *env,
                                          CanSILFunctionType fnType,
                                          irgen::IRGenModule &IGM,
                                          bool mustTransform) {
  if (!modifiableFunction(fnType) && !mustTransform) {
    return fnType;
  }
  
  // Map substituted function types according to their substituted generic
  // signature.
  if (fnType->getPatternSubstitutions()) {
    env = getSubstGenericEnvironment(fnType);
  }
  
  auto newParams = getNewParameters(env, fnType, IGM);
  auto newYields = getNewYields(env, fnType, IGM);
  auto newResults = getNewResults(env, fnType, IGM, mustTransform);
  auto newFnType = SILFunctionType::get(
      fnType->getInvocationGenericSignature(),
      fnType->getExtInfo(),
      fnType->getCoroutineKind(),
      fnType->getCalleeConvention(),
      newParams,
      newYields,
      newResults,
      fnType->getOptionalErrorResult(),
      fnType->getPatternSubstitutions(),
      fnType->getInvocationSubstitutions(),
      fnType->getASTContext(),
      fnType->getWitnessMethodConformanceOrInvalid());
  return newFnType;
}

SILType
LargeSILTypeMapper::getNewOptionalFunctionType(GenericEnvironment *GenericEnv,
                                               SILType storageType,
                                               irgen::IRGenModule &Mod) {
  SILType newSILType = storageType;
  if (auto objectType = storageType.getOptionalObjectType()) {
    if (auto fnType = objectType.getAs<SILFunctionType>()) {
      if (shouldTransformFunctionType(GenericEnv, fnType, Mod)) {
        auto newFnType = getNewSILFunctionType(GenericEnv, fnType, Mod);        
        newSILType =
          SILType::getPrimitiveType(newFnType, storageType.getCategory());
        newSILType = SILType::getOptionalType(newSILType);
      }
    }
  }
  return newSILType;
}

bool LargeSILTypeMapper::shouldTransformResults(GenericEnvironment *genEnv,
                                                CanSILFunctionType loweredTy,
                                                irgen::IRGenModule &Mod) {
  if (!modifiableFunction(loweredTy)) {
    return false;
  }

  if (loweredTy->getNumResults() != 1) {
    auto resultType = loweredTy->getAllResultsInterfaceType();
    auto newResultType = getNewSILType(genEnv, resultType, Mod);
    return resultType != newResultType;
  }

  auto singleResult = loweredTy->getSingleResult();
  auto resultStorageType = singleResult.getSILStorageInterfaceType();
  auto newResultStorageType = getNewSILType(genEnv, resultStorageType, Mod);
  if (resultStorageType != newResultStorageType) {
    return true;
  }
  return modNonFuncTypeResultType(genEnv, loweredTy, Mod);
}

static bool modResultType(SILFunction *F, irgen::IRGenModule &Mod,
                          LargeSILTypeMapper &Mapper) {
  GenericEnvironment *genEnv = getSubstGenericEnvironment(F);
  auto loweredTy = F->getLoweredFunctionType();

  return Mapper.shouldTransformResults(genEnv, loweredTy, Mod);
}

static bool shouldTransformYields(GenericEnvironment *genEnv,
                                  CanSILFunctionType loweredTy,
                                  irgen::IRGenModule &Mod,
                                  LargeSILTypeMapper &Mapper,
                                  TypeExpansionContext expansion) {
  if (!modifiableFunction(loweredTy)) {
    return false;
  }
  for (auto &yield : loweredTy->getYields()) {
    auto yieldStorageType = yield.getSILStorageType(Mod.getSILModule(),
                                                    loweredTy, expansion);
    auto newYieldStorageType =
        Mapper.getNewSILType(genEnv, yieldStorageType, Mod);
    if (yieldStorageType != newYieldStorageType)
      return true;
  }
  return false;
}

static bool modYieldType(SILFunction *F, irgen::IRGenModule &Mod,
                         LargeSILTypeMapper &Mapper) {
  GenericEnvironment *genEnv = getSubstGenericEnvironment(F);
  auto loweredTy = F->getLoweredFunctionType();

  return shouldTransformYields(genEnv, loweredTy, Mod, Mapper,
                               F->getTypeExpansionContext());
}

SILParameterInfo LargeSILTypeMapper::getNewParameter(GenericEnvironment *env,
                                                     SILParameterInfo param,
                                                     irgen::IRGenModule &IGM) {
  SILType storageType = param.getSILStorageInterfaceType();
  SILType newOptFuncType =
      getNewOptionalFunctionType(env, storageType, IGM);
  if (newOptFuncType != storageType) {
    return param.getWithInterfaceType(newOptFuncType.getASTType());
  }

  if (auto paramFnType = storageType.getAs<SILFunctionType>()) {
    if (shouldTransformFunctionType(env, paramFnType, IGM)) {
      auto newFnType = getNewSILFunctionType(env, paramFnType, IGM);
      return param.getWithInterfaceType(newFnType);
    } else {
      return param;
    }
  } else if (isLargeLoadableType(env, storageType, IGM)) {
    if (param.getConvention() == ParameterConvention::Direct_Owned) {
      return SILParameterInfo(storageType.getASTType(),
                              ParameterConvention::Indirect_In,
                              param.getOptions());
    }
    assert(param.getConvention() == ParameterConvention::Direct_Guaranteed
           || param.getConvention() == ParameterConvention::Direct_Unowned);
    return SILParameterInfo(storageType.getASTType(),
                            ParameterConvention::Indirect_In_Guaranteed,
                            param.getOptions());
  } else {
    auto newType = getNewSILType(env, storageType, IGM);
    return SILParameterInfo(newType.getASTType(), param.getConvention(),
                            param.getOptions());
  }
}

SmallVector<SILParameterInfo, 4>
LargeSILTypeMapper::getNewParameters(GenericEnvironment *env,
                                     CanSILFunctionType fnType,
                                     irgen::IRGenModule &IGM) {
  SmallVector<SILParameterInfo, 4> newParams;
  for (SILParameterInfo param : fnType->getParameters()) {
    auto newParam = getNewParameter(env, param, IGM);
    newParams.push_back(newParam);
  }
  return newParams;
}

SmallVector<SILYieldInfo, 2>
LargeSILTypeMapper::getNewYields(GenericEnvironment *env,
                                 CanSILFunctionType fnType,
                                 irgen::IRGenModule &IGM) {
  SmallVector<SILYieldInfo, 2> newYields;
  for (auto oldYield : fnType->getYields()) {
    auto newYieldAsParam = getNewParameter(env, oldYield, IGM);
    newYields.push_back(SILYieldInfo(newYieldAsParam.getInterfaceType(),
                                     newYieldAsParam.getConvention()));
  }
  return newYields;
}

SILType LargeSILTypeMapper::getNewTupleType(GenericEnvironment *GenericEnv,
                                            irgen::IRGenModule &Mod,
                                            const SILType &nonOptionalType,
                                            const SILType &storageType) {
  auto origType = nonOptionalType.getAs<TupleType>();
  assert(origType && "Expected a tuple type");
  SmallVector<TupleTypeElt, 2> newElems;
  for (TupleTypeElt canElem : origType->getElements()) {
    auto origCanType = CanType(canElem.getType());
    auto elem = SILType::getPrimitiveObjectType(origCanType);
    auto newElem = getNewSILType(GenericEnv, elem, Mod);
    newElems.emplace_back(newElem.getASTType(), canElem.getName());
  }
  auto type = TupleType::get(newElems, nonOptionalType.getASTContext());
  auto canType = CanType(type);
  SILType newSILType = SILType::getPrimitiveObjectType(canType);
  if (nonOptionalType.isAddress()) {
    newSILType = newSILType.getAddressType();
  }
  if (nonOptionalType != storageType) {
    newSILType = SILType::getOptionalType(newSILType);
  }
  if (storageType.isAddress()) {
    newSILType = newSILType.getAddressType();
  }
  return newSILType;
}

SILType LargeSILTypeMapper::getNewSILType(GenericEnvironment *GenericEnv,
                                          SILType storageType,
                                          irgen::IRGenModule &Mod) {
  // See if the type is already in the cache:
  auto typePair = std::make_pair(GenericEnv, storageType);
  if (oldToNewTypeMap.find(typePair) != oldToNewTypeMap.end()) {
    return oldToNewTypeMap[typePair];
  }

  SILType nonOptionalType = storageType;
  if (auto optType = storageType.getOptionalObjectType()) {
    nonOptionalType = optType;
  }
  if (nonOptionalType.getAs<TupleType>()) {
    SILType newSILType =
        getNewTupleType(GenericEnv, Mod, nonOptionalType, storageType);
    auto typeToRet = isLargeLoadableType(GenericEnv, newSILType, Mod)
                         ? newSILType.getAddressType()
                         : newSILType;
    oldToNewTypeMap[typePair] = typeToRet;
    return typeToRet;
  }
  SILType newSILType = getNewOptionalFunctionType(GenericEnv, storageType, Mod);
  if (newSILType != storageType) {
    oldToNewTypeMap[typePair] = newSILType;
    return newSILType;
  }
  if (auto fnType = storageType.getAs<SILFunctionType>()) {
    if (shouldTransformFunctionType(GenericEnv, fnType, Mod)) {
      auto newFnType = getNewSILFunctionType(GenericEnv, fnType, Mod);
      newSILType = SILType::getPrimitiveType(newFnType,
                                             storageType.getCategory());
    }
  } else if (isLargeLoadableType(GenericEnv, storageType, Mod)) {
    newSILType = storageType.getAddressType();
  }
  oldToNewTypeMap[typePair] = newSILType;
  return newSILType;
}

//===----------------------------------------------------------------------===//
// StructLoweringState: shared state for the pass's analysis and transforms.
//===----------------------------------------------------------------------===//

namespace {
struct StructLoweringState {
  SILFunction *F;
  irgen::IRGenModule &Mod;
  LargeSILTypeMapper &Mapper;

  // All large loadable function arguments that we modified
  SmallVector<SILValue, 16> largeLoadableArgs;
  // All modified function signature function arguments
  SmallVector<SILValue, 16> funcSigArgs;
  // All args for which we did a load
  llvm::MapVector<SILValue, SILValue> argsToLoadedValueMap;
  // All applies for which we did an alloc
  llvm::MapVector<SILInstruction *, SILValue> applyRetToAllocMap;
  // reverse map of the one above
  llvm::MapVector<SILInstruction *, SILInstruction *> allocToApplyRetMap;
  // All call sites with SILArgument that needs to be re-written
  // Calls are removed from the set when rewritten.
  SmallVector<SILInstruction *, 16> applies;
  // All MethodInst that use the large struct
  SmallVector<MethodInst *, 16> methodInstsToMod;
  // Large loadable store instrs should call the outlined copy
  SmallVector<StoreInst *, 16> storeInstsToMod;
  // All switch_enum instrs that should be converted to switch_enum_addr
  SmallVector<SwitchEnumInst *, 16> switchEnumInstsToMod;
  // All struct_extract instrs that should be converted to struct_element_addr
  SmallVector<StructExtractInst *, 16> structExtractInstsToMod;
  // All tuple instructions for which the return type is a function type
  SmallVector<SingleValueInstruction *, 8> tupleInstsToMod;
  // All allock stack instructions to modify
  SmallVector<AllocStackInst *, 8> allocStackInstsToMod;
  // All pointer to address instructions to modify
  SmallVector<PointerToAddressInst *, 8> pointerToAddrkInstsToMod;
  // All Retain and release instrs should be replaced with _addr version
  SmallVector<RetainValueInst *, 16> retainInstsToMod;
  SmallVector<ReleaseValueInst *, 16> releaseInstsToMod;
  // All result types instrs for which we need to convert the ResultTy
  llvm::SetVector<SingleValueInstruction *> resultTyInstsToMod;
  // All instructions that use the large struct that are not covered above
  SmallVector<SILInstruction *, 16> instsToMod;
  // All function-exiting terminators (return or throw instructions).
  SmallVector<TermInst *, 8> returnInsts;
  // All (large type) return instructions that are modified
  SmallVector<ReturnInst *, 8> modReturnInsts;
  // All (large type) yield instructions that are modified
  SmallVector<YieldInst *, 8> modYieldInsts;
  // All destroy_value instrs should be replaced with _addr version
  SmallVector<SILInstruction *, 16> destroyValueInstsToMod;
  // All make_borrow instrs that should be converted to make_addr_borrow
  SmallVector<MakeBorrowInst *, 16> makeBorrowInstsToMod;
  // All dereference_borrow instrs that should be converted to dereference_addr_borrow
  SmallVector<DereferenceBorrowInst *, 16> dereferenceBorrowInstsToMod;
  // All debug uses.
  // to be modified *only if* the operands are used in "real" instructions
  SmallVector<Operand *, 16> debugUsesToMod;

  StructLoweringState(SILFunction *F, irgen::IRGenModule &Mod,
                      LargeSILTypeMapper &Mapper)
      : F(F), Mod(Mod), Mapper(Mapper) {}

  bool isLargeLoadableType(CanSILFunctionType fnTy, SILType type) {
    return ::isLargeLoadableType(getSubstGenericEnvironment(fnTy), type, Mod);
  }

  SILType getNewSILType(CanSILFunctionType fnTy, SILType type) {
    return Mapper.getNewSILType(getSubstGenericEnvironment(fnTy), type, Mod);
  }

  bool containsDifferentFunctionSignature(CanSILFunctionType fnTy,
                                          SILType type) {
    return Mapper.containsDifferentFunctionSignature(
        getSubstGenericEnvironment(fnTy), Mod, type, getNewSILType(fnTy, type));
  }

  bool hasLargeLoadableYields() {
    auto fnType = F->getLoweredFunctionType();
    if (!fnType->isCoroutine()) return false;

    auto env = getSubstGenericEnvironment(fnType);
    for (auto yield : fnType->getYields()) {
      if (Mapper.shouldTransformParameter(env, yield, Mod))
        return true;
    }
    return false;
  }
};
} // end anonymous namespace

//===----------------------------------------------------------------------===//
// LargeValueVisitor: Map large loadable values to ValueStorage.
//===----------------------------------------------------------------------===//

namespace {
class LargeValueVisitor {
  StructLoweringState &pass;
  PostOrderFunctionInfo postorderInfo;

public:
  explicit LargeValueVisitor(StructLoweringState &pass)
      : pass(pass), postorderInfo(pass.F) {}

  void mapReturnInstrs();
  void mapValueStorage();

protected:
  void visitApply(ApplySite applySite);
  void visitMethodInst(MethodInst *instr);
  void visitStoreInst(StoreInst *instr);
  void visitSwitchEnumInst(SwitchEnumInst *instr);
  void visitStructExtractInst(StructExtractInst *instr);
  void visitRetainInst(RetainValueInst *instr);
  void visitReleaseInst(ReleaseValueInst *instr);
  void visitResultTyInst(SingleValueInstruction *instr);
  void visitDebugValueInst(DebugValueInst *instr);
  void visitDestroyValueInst(DestroyValueInst *instr);
  void visitTupleInst(SingleValueInstruction *instr);
  void visitAllocStackInst(AllocStackInst *instr);
  void visitPointerToAddressInst(PointerToAddressInst *instr);
  void visitReturnInst(ReturnInst *instr);
  void visitYieldInst(YieldInst *instr);
  void visitDeallocInst(DeallocStackInst *instr);
  void visitMakeBorrowInst(MakeBorrowInst *instr);
  void visitDereferenceBorrowInst(DereferenceBorrowInst *instr);
  void visitInstr(SILInstruction *instr);
};
} // end anonymous namespace

void LargeValueVisitor::mapReturnInstrs() {
  for (auto *BB : postorderInfo.getReversePostOrder()) {
    if (BB->getTerminator()->isFunctionExiting())
      pass.returnInsts.push_back(BB->getTerminator());
  }
}

void LargeValueVisitor::mapValueStorage() {
  for (auto *BB : postorderInfo.getReversePostOrder()) {
    for (auto &II : *BB) {
      SILInstruction *currIns = &II;
      switch (currIns->getKind()) {
      case SILInstructionKind::ApplyInst:
      case SILInstructionKind::TryApplyInst:
      case SILInstructionKind::BeginApplyInst:
      case SILInstructionKind::PartialApplyInst: {
        visitApply(ApplySite(currIns));
        break;
      }
      case SILInstructionKind::ClassMethodInst:
      case SILInstructionKind::SuperMethodInst:
      case SILInstructionKind::ObjCMethodInst:
      case SILInstructionKind::ObjCSuperMethodInst:
      case SILInstructionKind::WitnessMethodInst: {
        // TODO Any more instructions to add here?
        auto *MI = cast<MethodInst>(currIns);
        visitMethodInst(MI);
        break;
      }
      case SILInstructionKind::StructExtractInst:
      case SILInstructionKind::StructElementAddrInst:
      case SILInstructionKind::RefTailAddrInst:
      case SILInstructionKind::RefElementAddrInst:
      case SILInstructionKind::BeginAccessInst:
      case SILInstructionKind::InitEnumDataAddrInst:
      case SILInstructionKind::EnumInst:
      case SILInstructionKind::MakeAddrBorrowInst:
      case SILInstructionKind::DereferenceAddrBorrowInst:
      case SILInstructionKind::DereferenceBorrowAddrInst: {
        // TODO Any more instructions to add here?
        visitResultTyInst(cast<SingleValueInstruction>(currIns));
        break;
      }
      case SILInstructionKind::StoreInst: {
        auto *SI = cast<StoreInst>(currIns);
        visitStoreInst(SI);
        break;
      }
      case SILInstructionKind::RetainValueInst: {
        auto *RETI = cast<RetainValueInst>(currIns);
        visitRetainInst(RETI);
        break;
      }
      case SILInstructionKind::ReleaseValueInst: {
        auto *RELI = cast<ReleaseValueInst>(currIns);
        visitReleaseInst(RELI);
        break;
      }
      case SILInstructionKind::DebugValueInst: {
        auto *DI = cast<DebugValueInst>(currIns);
        visitDebugValueInst(DI);
        break;
      }
      case SILInstructionKind::DestroyValueInst: {
        auto *DI = cast<DestroyValueInst>(currIns);
        visitDestroyValueInst(DI);
        break;
      }
      case SILInstructionKind::SwitchEnumInst: {
        auto *SEI = cast<SwitchEnumInst>(currIns);
        visitSwitchEnumInst(SEI);
        break;
      }
      case SILInstructionKind::TupleElementAddrInst:
      case SILInstructionKind::TupleExtractInst: {
        visitTupleInst(cast<SingleValueInstruction>(currIns));
        break;
      }
      case SILInstructionKind::AllocStackInst: {
        auto *ASI = cast<AllocStackInst>(currIns);
        visitAllocStackInst(ASI);
        break;
      }
      case SILInstructionKind::PointerToAddressInst: {
        auto *PTA = cast<PointerToAddressInst>(currIns);
        visitPointerToAddressInst(PTA);
        break;
      }
      case SILInstructionKind::ReturnInst: {
        auto *RI = cast<ReturnInst>(currIns);
        visitReturnInst(RI);
        break;
      }
      case SILInstructionKind::YieldInst: {
        auto *YI = cast<YieldInst>(currIns);
        visitYieldInst(YI);
        break;
      }
      case SILInstructionKind::DeallocStackInst: {
        auto *DI = cast<DeallocStackInst>(currIns);
        visitDeallocInst(DI);
        break;
      }
      case SILInstructionKind::MakeBorrowInst: {
        auto *MBI = cast<MakeBorrowInst>(currIns);
        visitMakeBorrowInst(MBI);
        break;
      }
      case SILInstructionKind::DereferenceBorrowInst: {
        auto *DBI = cast<DereferenceBorrowInst>(currIns);
        visitDereferenceBorrowInst(DBI);
        break;
      }
      default: {
        assert(!ApplySite::isa(currIns) && "Did not expect an ApplySite");
        assert(!isa<MethodInst>(currIns) && "Unhandled Method Inst");
        visitInstr(currIns);
        break;
      }
      }
    }
  }
}

static bool modifiableApply(ApplySite applySite, irgen::IRGenModule &Mod) {
  // If the callee is a method then use the old ABI
  if (applySite.getSubstCalleeType()->getLanguage() == SILFunctionLanguage::C) {
    return false;
  }
  SILValue callee = applySite.getCallee();
  if (auto site = ApplySite::isa(callee)) {
    return modifiableApply(site, Mod);
  }
  return true;
}

void LargeValueVisitor::visitApply(ApplySite applySite) {
  if (!modifiableApply(applySite, pass.Mod)) {
    return visitInstr(applySite.getInstruction());
  }
  for (Operand &operand : applySite.getArgumentOperands()) {
    SILValue currOperand = operand.get();
    SILType silType = currOperand->getType();
    SILType newSilType = pass.getNewSILType(applySite.getSubstCalleeType(),
                                            silType);
    if (silType != newSilType ||
        std::find(pass.largeLoadableArgs.begin(), pass.largeLoadableArgs.end(),
                  currOperand) != pass.largeLoadableArgs.end() ||
        std::find(pass.funcSigArgs.begin(), pass.funcSigArgs.end(),
                  currOperand) != pass.funcSigArgs.end()) {
      pass.applies.push_back(applySite.getInstruction());
      return;
    }
  }

  // For coroutines, we need to consider the yields, not the direct result
  // (which should always be void).
  if (auto beginApply = dyn_cast<BeginApplyInst>(applySite)) {
    for (auto yield : beginApply->getYieldedValues()) {
      auto oldYieldType = yield->getType();
      auto newYieldType = pass.getNewSILType(beginApply->getSubstCalleeType(),
                                             oldYieldType);
      if (oldYieldType != newYieldType) {
        pass.applies.push_back(applySite.getInstruction());
        return;
      }
    }
    return;
  }

  SILType currType = applySite.getType();
  SILType newType = pass.getNewSILType(pass.F->getLoweredFunctionType(),
                                       currType);
  // We only care about function type results
  if (!pass.isLargeLoadableType(pass.F->getLoweredFunctionType(),
                                currType) &&
      (currType != newType)) {
    pass.applies.push_back(applySite.getInstruction());
    return;
  }
  // Check callee - need new generic env:
  CanSILFunctionType origSILFunctionType = applySite.getSubstCalleeType();
  GenericEnvironment *genEnvCallee = nullptr;
  auto newSILFunctionType = pass.Mapper.getNewSILFunctionType(
      genEnvCallee, origSILFunctionType, pass.Mod);
  if (origSILFunctionType != newSILFunctionType) {
    pass.applies.push_back(applySite.getInstruction());
  }
}

static bool isMethodInstUnmodifiable(MethodInst *instr) {
  for (auto *user : instr->getUses()) {
    if (ApplySite::isa(user->getUser())) {
      ApplySite applySite = ApplySite(user->getUser());
      if (applySite.getSubstCalleeType()->getLanguage() ==
          SILFunctionLanguage::C) {
        return true;
      }
    }
  }
  return false;
}

void LargeValueVisitor::visitMethodInst(MethodInst *instr) {
  if (isMethodInstUnmodifiable(instr)) {
    // Do not change the method!
    visitInstr(instr);
    return;
  }
  SILType currSILType = instr->getType();
  auto fnType = currSILType.castTo<SILFunctionType>();

  GenericEnvironment *genEnv = nullptr;
  if (fnType->isPolymorphic()) {
    genEnv = getSubstGenericEnvironment(fnType);
  }
  if (pass.Mapper.shouldTransformFunctionType(genEnv, fnType, pass.Mod)) {
    pass.methodInstsToMod.push_back(instr);
    return;
  }
  if (pass.Mapper.newResultsDiffer(genEnv, fnType->getResults(), pass.Mod)) {
    pass.methodInstsToMod.push_back(instr);
  }
}

void LargeValueVisitor::visitStoreInst(StoreInst *instr) {
  SILValue src = instr->getSrc();
  if (std::find(pass.largeLoadableArgs.begin(), pass.largeLoadableArgs.end(),
                src) != pass.largeLoadableArgs.end()) {
    pass.storeInstsToMod.push_back(instr);
  }
}

bool LargeSILTypeMapper::shouldConvertBBArg(SILArgument *arg,
                                            irgen::IRGenModule &Mod) {
  auto *F = arg->getFunction();
  SILType storageType = arg->getType();
  GenericEnvironment *genEnv = getSubstGenericEnvironment(F);
  auto currCanType = storageType.getASTType();
  if (auto funcType = dyn_cast<SILFunctionType>(currCanType)) {
    if (funcType->isPolymorphic()) {
      genEnv = getSubstGenericEnvironment(funcType);
    }
  }
  SILType newSILType = getNewSILType(genEnv, storageType, Mod);
  // We (currently) only care about function signatures
  if (containsDifferentFunctionSignature(genEnv, Mod, storageType,
                                         newSILType)) {
    return true;
  }
  return false;
}

void LargeValueVisitor::visitSwitchEnumInst(SwitchEnumInst *instr) {
  SILValue operand = instr->getOperand();
  if (std::find(pass.largeLoadableArgs.begin(), pass.largeLoadableArgs.end(),
                operand) != pass.largeLoadableArgs.end()) {
    pass.switchEnumInstsToMod.push_back(instr);
    return;
  }
  // In case we converted the target BB type of this enum,
  // to an address based one - need to modify
  unsigned numOfCases = instr->getNumCases();
  SmallVector<std::pair<EnumElementDecl *, SILBasicBlock *>, 16> caseBBs;
  for (unsigned i = 0; i < numOfCases; ++i) {
    auto currCase = instr->getCase(i);
    auto *currBB = currCase.second;
    for (SILArgument *arg : currBB->getArguments()) {
      if (pass.Mapper.shouldConvertBBArg(arg, pass.Mod)) {
        SILType storageType = arg->getType();
        SILType newSILType = pass.getNewSILType(pass.F->getLoweredFunctionType(),
                                                storageType);
        if (newSILType.isAddress()) {
          pass.switchEnumInstsToMod.push_back(instr);
          return;
        }
      }
    }
  }
}

void LargeValueVisitor::visitStructExtractInst(StructExtractInst *instr) {
  SILValue operand = instr->getOperand();
  if (std::find(pass.largeLoadableArgs.begin(), pass.largeLoadableArgs.end(),
                operand) != pass.largeLoadableArgs.end()) {
    pass.structExtractInstsToMod.push_back(instr);
  }
}

void LargeValueVisitor::visitMakeBorrowInst(MakeBorrowInst *instr) {
  SILValue operand = instr->getOperand();

  // Value borrows of large types must be changed into address borrows, since
  // IRGen will use the address representation.
  if (pass.isLargeLoadableType(pass.F->getLoweredFunctionType(),
                               operand->getType().getObjectType())) {
    pass.makeBorrowInstsToMod.push_back(instr);
    return;
  }
}

void LargeValueVisitor::visitRetainInst(RetainValueInst *instr) {
  for (Operand &operand : instr->getAllOperands()) {
    if (std::find(pass.largeLoadableArgs.begin(), pass.largeLoadableArgs.end(),
                  operand.get()) != pass.largeLoadableArgs.end()) {
      pass.retainInstsToMod.push_back(instr);
      return;
    }
  }
}

void LargeValueVisitor::visitReleaseInst(ReleaseValueInst *instr) {
  for (Operand &operand : instr->getAllOperands()) {
    if (std::find(pass.largeLoadableArgs.begin(), pass.largeLoadableArgs.end(),
                  operand.get()) != pass.largeLoadableArgs.end()) {
      pass.releaseInstsToMod.push_back(instr);
      return;
    }
  }
}

void LargeValueVisitor::visitDebugValueInst(DebugValueInst *instr) {
  for (Operand &operand : instr->getAllOperands()) {
    if (std::find(pass.largeLoadableArgs.begin(), pass.largeLoadableArgs.end(),
                  operand.get()) != pass.largeLoadableArgs.end()) {
      pass.debugUsesToMod.push_back(&operand);
    }
  }
}

void LargeValueVisitor::visitDestroyValueInst(DestroyValueInst *instr) {
  for (Operand &operand : instr->getAllOperands()) {
    if (std::find(pass.largeLoadableArgs.begin(), pass.largeLoadableArgs.end(),
                  operand.get()) != pass.largeLoadableArgs.end()) {
      pass.destroyValueInstsToMod.push_back(instr);
    }
  }
}

void LargeValueVisitor::
visitDereferenceBorrowInst(DereferenceBorrowInst *instr) {
  // Value borrows of large types must be changed into address borrows, since
  // IRGen will use the address representation.
  if (pass.isLargeLoadableType(pass.F->getLoweredFunctionType(),
                               instr->getType().getObjectType())) {
    pass.dereferenceBorrowInstsToMod.push_back(instr);
    return;
  }
}

void LargeValueVisitor::visitResultTyInst(SingleValueInstruction *instr) {
  SILType currSILType = instr->getType().getObjectType();
  SILType newSILType = pass.getNewSILType(pass.F->getLoweredFunctionType(),
                                          currSILType);
  if (currSILType != newSILType) {
    pass.resultTyInstsToMod.insert(instr);
  }
  if (auto *SEI = dyn_cast<StructExtractInst>(instr)) {
    visitStructExtractInst(SEI);
  } else {
    visitInstr(instr);
  }
}

void LargeValueVisitor::visitTupleInst(SingleValueInstruction *instr) {
  SILType currSILType = instr->getType().getObjectType();
  if (auto funcType = getInnerFunctionType(currSILType)) {
    GenericEnvironment *genEnv = getSubstGenericEnvironment(instr->getFunction());
    if (!genEnv && funcType->isPolymorphic()) {
      genEnv = getSubstGenericEnvironment(funcType);
    }
    auto newSILFunctionType =
        pass.Mapper.getNewSILFunctionType(genEnv, funcType, pass.Mod);
    if (funcType != newSILFunctionType) {
      pass.tupleInstsToMod.push_back(instr);
    }
  }
  visitInstr(instr);
}

void LargeValueVisitor::visitAllocStackInst(AllocStackInst *instr) {
  SILType currSILType = instr->getType().getObjectType();
  if (pass.containsDifferentFunctionSignature(pass.F->getLoweredFunctionType(),
                                              currSILType)) {
    pass.allocStackInstsToMod.push_back(instr);
  }
}

void LargeValueVisitor::visitPointerToAddressInst(PointerToAddressInst *instr) {
  SILType currSILType = instr->getType().getObjectType();
  if (pass.containsDifferentFunctionSignature(pass.F->getLoweredFunctionType(),
                                              currSILType)) {
    pass.pointerToAddrkInstsToMod.push_back(instr);
  }
}

static bool modNonFuncTypeResultType(SILFunction *F, irgen::IRGenModule &Mod) {
  GenericEnvironment *genEnv = getSubstGenericEnvironment(F);
  auto loweredTy = F->getLoweredFunctionType();
  return modNonFuncTypeResultType(genEnv, loweredTy, Mod);
}

void LargeValueVisitor::visitReturnInst(ReturnInst *instr) {
  if (!modResultType(pass.F, pass.Mod, pass.Mapper)) {
    visitInstr(instr);
  } else if (modNonFuncTypeResultType(pass.F, pass.Mod)) {
    pass.modReturnInsts.push_back(instr);
  } // else: function signature return instructions remain as-is
}

void LargeValueVisitor::visitYieldInst(YieldInst *instr) {
  if (!modYieldType(pass.F, pass.Mod, pass.Mapper)) {
    visitInstr(instr);
  } else {
    pass.modYieldInsts.push_back(instr);
  } // else: function signature return instructions remain as-is
}

void LargeValueVisitor::visitDeallocInst(DeallocStackInst *instr) {
  auto opInstr = instr->getOperand();
  if (std::find(pass.largeLoadableArgs.begin(), pass.largeLoadableArgs.end(),
                opInstr) != pass.largeLoadableArgs.end()) {
    auto *opAsInstr = dyn_cast<AllocStackInst>(opInstr);
    assert(opAsInstr && "Expected an alloc stack instruction");
    assert(pass.allocToApplyRetMap.find(opAsInstr) !=
               pass.allocToApplyRetMap.end() &&
           "Unexpected dealloc instr!");
    (void)opAsInstr;
  }
}

void LargeValueVisitor::visitInstr(SILInstruction *instr) {
  for (Operand &operand : instr->getAllOperands()) {
    if (std::find(pass.largeLoadableArgs.begin(), pass.largeLoadableArgs.end(),
                  operand.get()) != pass.largeLoadableArgs.end()) {
      pass.instsToMod.push_back(instr);
      // will be replaced later by the load / alloc_stack:
      pass.argsToLoadedValueMap[operand.get()] = operand.get();
    }
  }
}

//===----------------------------------------------------------------------===//
// LoadableStorageAllocation: Generate alloc_stack and address projections
// for all loadable types we pass around.
//===----------------------------------------------------------------------===//

namespace {
class LoadableStorageAllocation {
public:
  StructLoweringState &pass;

  explicit LoadableStorageAllocation(StructLoweringState &pass) : pass(pass) {}

  void allocateLoadableStorage();
  void replaceLoad(LoadInst *unoptimizableLoad);

protected:
  void replaceLoadWithCopyAddr(LoadInst *optimizableLoad);
  void replaceLoadWithCopyAddrForModifiable(LoadInst *unoptimizableLoad);
  void convertIndirectFunctionArgs();
  void insertIndirectReturnArgs();
  void convertIndirectFunctionPointerArgsForUnmodifiable();
  void convertIndirectBasicBlockArgs();
  void convertApplyResults();
  void allocateForArg(SILValue value);
  AllocStackInst *allocateForApply(SILInstruction *apply, SILType type);
  SILArgument *replaceArgType(SILBuilder &argBuilder, SILArgument *arg,
                              SILType newSILType);
};
} // end anonymous namespace

static AllocStackInst *allocate(StructLoweringState &pass, SILType type) {
  assert(type.isObject());

  // Insert an alloc_stack at the beginning of the function.
  SILBuilderWithScope allocBuilder(&*pass.F->begin());
  // Don't put any variable debug info into the alloc_stack, there will be a
  // debug_value inserted later. TODO: It may be more elegant to insert
  // the variable info into the alloc_stack instead of additionally generating a
  // debug_value.
  AllocStackInst *alloc = allocBuilder.createAllocStack(
      RegularLocation::getAutoGeneratedLocation(), type);

  // Insert dealloc_stack at the end(s) of the function.
  for (TermInst *termInst : pass.returnInsts) {
    SILBuilderWithScope deallocBuilder(termInst);
    deallocBuilder.createDeallocStack(
        RegularLocation::getAutoGeneratedLocation(), alloc);
  }

  return alloc;
}

static StoreOwnershipQualifier
getStoreInitOwnership(StructLoweringState &pass, SILType type) {
  if (!pass.F->hasOwnership()) {
    return StoreOwnershipQualifier::Unqualified;
  } else if (type.isTrivial(*pass.F)) {
    return StoreOwnershipQualifier::Trivial;
  } else {
    return StoreOwnershipQualifier::Init;
  }
}

static StoreInst *createStoreInit(StructLoweringState &pass,
                                  SILBasicBlock::iterator where,
                                  SILLocation loc,
                                  SILValue value, SILValue address) {
  SILBuilderWithScope storeBuilder(where);
  return storeBuilder.createStore(loc, value, address,
                                 getStoreInitOwnership(pass, value->getType()));
}

static SILInstruction *createOutlinedCopyCall(SILBuilder &copyBuilder,
                                              SILValue src, SILValue tgt,
                                              StructLoweringState &pass,
                                              SILLocation *loc = nullptr) {
  SILLocation locToUse = loc ? *loc : copyBuilder.getInsertionPoint()->getLoc();
  auto *copy =
      copyBuilder.createCopyAddr(locToUse, src, tgt, IsTake, IsInitialization);
  return copy;
}

void LoadableStorageAllocation::replaceLoadWithCopyAddr(
    LoadInst *optimizableLoad) {
  SILValue value = optimizableLoad->getOperand();

  auto allocInstr = allocate(pass, value->getType().getObjectType());

  SILBuilderWithScope outlinedBuilder(optimizableLoad);
  createOutlinedCopyCall(outlinedBuilder, value, allocInstr, pass);

  for (auto *user : optimizableLoad->getUses()) {
    SILInstruction *userIns = user->getUser();
    switch (userIns->getKind()) {
    case SILInstructionKind::CopyAddrInst:
    case SILInstructionKind::DeallocStackInst:
      break;
    case SILInstructionKind::ApplyInst:
    case SILInstructionKind::TryApplyInst:
    case SILInstructionKind::BeginApplyInst:
    case SILInstructionKind::PartialApplyInst: {
      if (std::find(pass.applies.begin(), pass.applies.end(), userIns) ==
          pass.applies.end()) {
        pass.applies.push_back(userIns);
      }
      break;
    }
    case SILInstructionKind::YieldInst:
      // The rewrite is enough.
      break;
    case SILInstructionKind::RetainValueInst: {
      auto *insToInsert = cast<RetainValueInst>(userIns);
      pass.retainInstsToMod.push_back(insToInsert);
      break;
    }
    case SILInstructionKind::ReleaseValueInst: {
      auto *insToInsert = cast<ReleaseValueInst>(userIns);
      pass.releaseInstsToMod.push_back(insToInsert);
      break;
    }
    case SILInstructionKind::StoreInst: {
      auto *insToInsert = cast<StoreInst>(userIns);
      pass.storeInstsToMod.push_back(insToInsert);
      break;
    }
    case SILInstructionKind::DebugValueInst: {
      pass.debugUsesToMod.push_back(user);
      break;
    }
    case SILInstructionKind::DestroyValueInst: {
      auto *insToInsert = cast<DestroyValueInst>(userIns);
      pass.destroyValueInstsToMod.push_back(insToInsert);
      break;
    }
    case SILInstructionKind::StructExtractInst: {
      auto *instToInsert = cast<StructExtractInst>(userIns);
      if (std::find(pass.structExtractInstsToMod.begin(),
                    pass.structExtractInstsToMod.end(),
                    instToInsert) == pass.structExtractInstsToMod.end()) {
        pass.structExtractInstsToMod.push_back(instToInsert);
      }
      break;
    }
    case SILInstructionKind::SwitchEnumInst: {
      auto *instToInsert = cast<SwitchEnumInst>(userIns);
      if (std::find(pass.switchEnumInstsToMod.begin(),
                    pass.switchEnumInstsToMod.end(),
                    instToInsert) == pass.switchEnumInstsToMod.end()) {
        pass.switchEnumInstsToMod.push_back(instToInsert);
      }
      break;
    }
    case SILInstructionKind::MakeBorrowInst: {
      auto *instToInsert = cast<MakeBorrowInst>(userIns);
      if (std::find(pass.makeBorrowInstsToMod.begin(),
                    pass.makeBorrowInstsToMod.end(),
                    instToInsert) == pass.makeBorrowInstsToMod.end()) {
        pass.makeBorrowInstsToMod.push_back(instToInsert);
      }
      break;
    }
    case SILInstructionKind::DereferenceBorrowInst: {
      auto *instToInsert = cast<DereferenceBorrowInst>(userIns);
      if (std::find(pass.dereferenceBorrowInstsToMod.begin(),
                    pass.dereferenceBorrowInstsToMod.end(),
                    instToInsert) == pass.dereferenceBorrowInstsToMod.end()) {
        pass.dereferenceBorrowInstsToMod.push_back(instToInsert);
      }
      break;
    }
    default:
      llvm_unreachable("Unexpected instruction");
    }
  }

  optimizableLoad->replaceAllUsesWith(allocInstr);
  optimizableLoad->getParent()->erase(optimizableLoad);
}

static bool isYieldUseRewritable(StructLoweringState &pass,
                                  YieldInst *inst, Operand *operand) {
  assert(inst == operand->getUser());
  return pass.isLargeLoadableType(pass.F->getLoweredFunctionType(),
                                  operand->get()->getType());
}

/// Does the value's uses contain instructions that *must* be rewrites?
static bool hasMandatoryRewriteUse(StructLoweringState &pass,
                                   SILValue value) {
  for (auto *user : value->getUses()) {
    SILInstruction *userIns = user->getUser();
    switch (userIns->getKind()) {
    case SILInstructionKind::ApplyInst:
    case SILInstructionKind::TryApplyInst:
    case SILInstructionKind::BeginApplyInst:
    case SILInstructionKind::PartialApplyInst: {
      ApplySite site(userIns);
      SILValue callee = site.getCallee();
      if (callee == value) {
        break;
      }
      SILType currType = value->getType().getObjectType();
      SILType newSILType = pass.getNewSILType(pass.F->getLoweredFunctionType(),
                                              currType);
      if (currType == newSILType) {
        break;
      }
      return true;
    }
    case SILInstructionKind::YieldInst:
      if (isYieldUseRewritable(pass, cast<YieldInst>(userIns), user))
        return true;
      break;
    default:
      break;
    }
  }
  return false;
}

void LoadableStorageAllocation::replaceLoadWithCopyAddrForModifiable(
    LoadInst *unoptimizableLoad) {
  if (!hasMandatoryRewriteUse(pass, unoptimizableLoad)) {
    return;
  }
  SILValue value = unoptimizableLoad->getOperand();

  AllocStackInst *alloc = allocate(pass, value->getType().getObjectType());

  SILBuilderWithScope outlinedBuilder(unoptimizableLoad);
  createOutlinedCopyCall(outlinedBuilder, value, alloc, pass);

  SmallVector<Operand *, 8> usesToMod;
  for (auto *use : unoptimizableLoad->getUses()) {
    SILInstruction *userIns = use->getUser();
    switch (userIns->getKind()) {
    case SILInstructionKind::CopyAddrInst:
    case SILInstructionKind::DeallocStackInst:
      break;
    case SILInstructionKind::ApplyInst:
    case SILInstructionKind::TryApplyInst:
    case SILInstructionKind::BeginApplyInst:
    case SILInstructionKind::PartialApplyInst: {
      ApplySite site(userIns);
      if (!modifiableApply(site, pass.Mod)) {
        break;
      }
      SILValue callee = site.getCallee();
      if (callee == unoptimizableLoad) {
        break;
      }
      SILType currType = unoptimizableLoad->getType().getObjectType();
      SILType newSILType = pass.getNewSILType(pass.F->getLoweredFunctionType(),
                                              currType);
      if (currType == newSILType) {
        break;
      }
      if (std::find(pass.applies.begin(), pass.applies.end(), userIns) ==
          pass.applies.end()) {
        pass.applies.push_back(userIns);
      }
      usesToMod.push_back(use);
      break;
    }
    case SILInstructionKind::YieldInst: {
      if (isYieldUseRewritable(pass, cast<YieldInst>(userIns), use))
        usesToMod.push_back(use);
      break;
    }
    case SILInstructionKind::RetainValueInst: {
      auto *insToInsert = cast<RetainValueInst>(userIns);
      pass.retainInstsToMod.push_back(insToInsert);
      usesToMod.push_back(use);
      break;
    }
    case SILInstructionKind::ReleaseValueInst: {
      auto *insToInsert = cast<ReleaseValueInst>(userIns);
      pass.releaseInstsToMod.push_back(insToInsert);
      usesToMod.push_back(use);
      break;
    }
    case SILInstructionKind::StoreInst: {
      auto *insToInsert = cast<StoreInst>(userIns);
      pass.storeInstsToMod.push_back(insToInsert);
      usesToMod.push_back(use);
      break;
    }
    case SILInstructionKind::DebugValueInst: {
      pass.debugUsesToMod.push_back(use);
      usesToMod.push_back(use);
      break;
    }
    case SILInstructionKind::DestroyValueInst: {
      auto *insToInsert = cast<DestroyValueInst>(userIns);
      pass.destroyValueInstsToMod.push_back(insToInsert);
      usesToMod.push_back(use);
      break;
    }
    case SILInstructionKind::StructExtractInst: {
      auto *instToInsert = cast<StructExtractInst>(userIns);
      pass.structExtractInstsToMod.push_back(instToInsert);
      usesToMod.push_back(use);
      break;
    }
    case SILInstructionKind::SwitchEnumInst: {
      auto *instToInsert = cast<SwitchEnumInst>(userIns);
      pass.switchEnumInstsToMod.push_back(instToInsert);
      usesToMod.push_back(use);
      break;
    }
    case SILInstructionKind::MakeBorrowInst: {
      auto *instToInsert = cast<MakeBorrowInst>(userIns);
      pass.makeBorrowInstsToMod.push_back(instToInsert);
      usesToMod.push_back(use);
      break;
    }
    case SILInstructionKind::DereferenceBorrowInst: {
      auto *instToInsert = cast<DereferenceBorrowInst>(userIns);
      pass.dereferenceBorrowInstsToMod.push_back(instToInsert);
      usesToMod.push_back(use);
      break;
    }
    default:
      break;
    }
  }
  while (!usesToMod.empty()) {
    auto *use = usesToMod.pop_back_val();
    use->set(alloc);
  }
}

void LoadableStorageAllocation::allocateLoadableStorage() {
  // We need to map all functions exits
  // required for Apply result's allocations
  // Else we might get the following error:
  // "stack dealloc does not match most recent stack alloc"
  // When we dealloc later
  LargeValueVisitor(pass).mapReturnInstrs();
  if (modifiableFunction(pass.F->getLoweredFunctionType())) {
    // Turn by-value function args to by-address ones
    convertIndirectFunctionArgs();
  } else {
    convertIndirectFunctionPointerArgsForUnmodifiable();
  }
  convertApplyResults();

  // Populate the pass' data structs
  LargeValueVisitor(pass).mapValueStorage();

  // Turn by-value BB args to by-address ones
  convertIndirectBasicBlockArgs();

  // Create an AllocStack for every used large loadable type in the function.
  for (auto &argToAlloc : pass.argsToLoadedValueMap) {
    assert(argToAlloc.first == argToAlloc.second);
    allocateForArg(argToAlloc.first);
  }
}

SILArgument *LoadableStorageAllocation::replaceArgType(SILBuilder &argBuilder,
                                                       SILArgument *arg,
                                                       SILType newSILType) {
  SILValue undef = SILUndef::get(pass.F, newSILType);
  SmallVector<Operand *, 8> useList(arg->use_begin(), arg->use_end());
  for (auto *use : useList) {
    use->set(undef);
  }

  // Make sure that this is an argument we want to replace.
  assert(std::find(pass.largeLoadableArgs.begin(), pass.largeLoadableArgs.end(),
                   arg) == pass.largeLoadableArgs.end());

  arg = arg->getParent()->replaceFunctionArgument(
      arg->getIndex(), newSILType, OwnershipKind::None, arg->getDecl());

  for (auto *use : useList) {
    use->set(arg);
  }

  return arg;
}

void LoadableStorageAllocation::insertIndirectReturnArgs() {
  GenericEnvironment *genEnv = getSubstGenericEnvironment(pass.F);
  auto loweredTy = pass.F->getLoweredFunctionType();
  SILType resultStorageType = loweredTy->getAllResultsSubstType(pass.F->getModule(),
                                                                pass.F->getTypeExpansionContext());
  auto canType = resultStorageType.getASTType();
  if (canType->hasTypeParameter()) {
    assert(genEnv && "Expected a GenericEnv");
    canType = genEnv->mapTypeIntoEnvironment(canType)->getCanonicalType();
  }
  resultStorageType = SILType::getPrimitiveObjectType(canType);
  auto newResultStorageType =
      pass.F->getLoweredType(pass.getNewSILType(loweredTy, resultStorageType));

  auto &ctx = pass.F->getModule().getASTContext();
  auto var = new (ctx) ParamDecl(
      SourceLoc(), SourceLoc(),
      ctx.getIdentifier("$return_value"), SourceLoc(),
      ctx.getIdentifier("$return_value"),
      pass.F->getDeclContext());
  var->setSpecifier(ParamSpecifier::InOut);
  pass.F->begin()->insertFunctionArgument(
      0, newResultStorageType.getAddressType(), OwnershipKind::None, var);
}

void LoadableStorageAllocation::convertIndirectFunctionArgs() {
  SILBasicBlock *entry = pass.F->getEntryBlock();
  SILBuilderWithScope argBuilder(entry->begin());

  for (SILArgument *arg : entry->getArguments()) {
    SILType storageType = arg->getType();
    SILType newSILType = pass.getNewSILType(pass.F->getLoweredFunctionType(),
                                            storageType);
    if (newSILType != storageType) {
      ValueOwnershipKind ownership = arg->getOwnershipKind();
      arg = replaceArgType(argBuilder, arg, newSILType);
      if (pass.isLargeLoadableType(pass.F->getLoweredFunctionType(),
                                   storageType)) {
        // Add to largeLoadableArgs if and only if it wasn't a modified function
        // signature arg
        pass.largeLoadableArgs.push_back(arg);
      } else {
        arg->setOwnershipKind(ownership);
        pass.funcSigArgs.push_back(arg);
      }
    }
  }

  // Convert the result type to indirect if necessary:
  if (modNonFuncTypeResultType(pass.F, pass.Mod) &&
      !pass.F->getConventionsInContext().hasGuaranteedResult()) {
    insertIndirectReturnArgs();
  }
}

static void convertBBArgType(SILBuilder &argBuilder, SILType newSILType,
                             SILArgument *arg) {
  SILValue undef = SILUndef::get(argBuilder.getFunction(), newSILType);
  SmallVector<Operand *, 8> useList(arg->use_begin(), arg->use_end());
  for (auto *use : useList) {
    use->set(undef);
  }

  arg = arg->getParent()->replacePhiArgument(arg->getIndex(), newSILType,
                                             arg->getOwnershipKind());
  for (auto *use : useList) {
    use->set(arg);
  }
}

static bool containsFunctionType(CanType ty) {
  if (auto tuple = dyn_cast<TupleType>(ty)) {
    for (auto elt : tuple.getElementTypes()) {
      if (containsFunctionType(elt))
        return true;
    }
    return false;
  }
  if (auto optionalType = ty.getOptionalObjectType()) {
    return containsFunctionType(optionalType);
  }
  return isa<SILFunctionType>(ty);
}

void LoadableStorageAllocation::convertApplyResults() {
  for (auto &BB : *pass.F) {
    for (auto &II : BB) {
      auto *currIns = &II;
      auto applySite = FullApplySite::isa(currIns);
      if (!applySite) {
        continue;
      }
      if (!modifiableApply(applySite, pass.Mod)) {
        continue;
      }

      CanSILFunctionType origSILFunctionType = applySite.getSubstCalleeType();
      GenericEnvironment *genEnv = getSubstGenericEnvironment(origSILFunctionType);
      if (!pass.Mapper.shouldTransformResults(genEnv, origSILFunctionType,
                                              pass.Mod)) {
        continue;
      }
      auto resultStorageType = origSILFunctionType->getAllResultsInterfaceType();
      if (!modNonFuncTypeResultType(genEnv, origSILFunctionType, pass.Mod)) {
        // Make sure it contains a function type
        auto numFuncTy = llvm::count_if(origSILFunctionType->getResults(),
            [](const SILResultInfo &origResult) {
              auto resultStorageTy = origResult.getSILStorageInterfaceType();
              return containsFunctionType(resultStorageTy.getASTType());
            });
        assert(numFuncTy != 0 &&
               "Expected a SILFunctionType inside the result Type");
        (void)numFuncTy;
        continue;
      }
      auto resultContextTy = origSILFunctionType->substInterfaceType(
          pass.F->getModule(), resultStorageType,
          pass.F->getTypeExpansionContext());
      auto newSILType = pass.getNewSILType(origSILFunctionType,
                                           resultContextTy);
      auto *newVal = allocateForApply(currIns, newSILType.getObjectType());
      if (auto apply = dyn_cast<ApplyInst>(currIns)) {
        apply->replaceAllUsesWith(newVal);
      } else {
        auto tryApplyIns = cast<TryApplyInst>(currIns);
        auto *normalBB = tryApplyIns->getNormalBB();
        SILBuilderWithScope argBuilder(normalBB->begin());
        assert(normalBB->getNumArguments() == 1 &&
               "Expected only one arg for try_apply normal BB");
        auto arg = normalBB->getArgument(0);
        arg->replaceAllUsesWith(newVal);
        auto emptyTy = SILType::getPrimitiveObjectType(
            TupleType::getEmpty(argBuilder.getModule().getASTContext()));
        convertBBArgType(argBuilder, emptyTy, arg);
      }
    }
  }
}

void LoadableStorageAllocation::
    convertIndirectFunctionPointerArgsForUnmodifiable() {
  SILBasicBlock *entry = pass.F->getEntryBlock();
  SILBuilderWithScope argBuilder(entry->begin());

  for (SILArgument *arg : entry->getArguments()) {
    SILType storageType = arg->getType();
    SILType newSILType = pass.getNewSILType(pass.F->getLoweredFunctionType(),
                                            storageType);
    if (pass.containsDifferentFunctionSignature(pass.F->getLoweredFunctionType(),
                                                storageType)) {
      auto *castInstr = argBuilder.createUncheckedReinterpretCast(
          RegularLocation(const_cast<ValueDecl *>(arg->getDecl())), arg,
          newSILType);
      arg->replaceAllUsesWith(castInstr);
      castInstr->setOperand(0, arg);
    }
  }
}

void LoadableStorageAllocation::convertIndirectBasicBlockArgs() {
  SILBasicBlock *entry = pass.F->getEntryBlock();
  for (SILBasicBlock &BB : *pass.F) {
    if (&BB == entry) {
      // Already took care of function args
      continue;
    }
    SILBuilderWithScope argBuilder(BB.begin());
    for (SILArgument *arg : BB.getArguments()) {
      if (!pass.Mapper.shouldConvertBBArg(arg, pass.Mod)) {
        continue;
      }
      SILType storageType = arg->getType();
      SILType newSILType = pass.getNewSILType(pass.F->getLoweredFunctionType(),
                                              storageType);
      // We don't change the type from object to address for function args:
      // a tuple with both a large type and a function arg should remain
      // as an object type for now
      if (storageType.isObject()) {
        newSILType = newSILType.getObjectType();
      }
      convertBBArgType(argBuilder, newSILType, arg);
    }
  }
}

void LoadableStorageAllocation::allocateForArg(SILValue value) {
  if (auto *allocInstr = dyn_cast<AllocStackInst>(value)) {
    // Special case: the value was already an Alloc
    // This happens in case of values from apply results (for example)
    // we *should* add a load for the current uses.
    // Said load should happen before the first use
    // As such add it right after the apply()
    LoadInst *load = nullptr;
    assert(pass.allocToApplyRetMap.find(allocInstr) !=
               pass.allocToApplyRetMap.end() &&
           "Alloc is not for apply results");
    auto *applyInst = pass.allocToApplyRetMap[allocInstr];
    assert(applyInst && "Value is not an apply");
    auto II = applyInst->getIterator();
    SILBuilderWithScope loadBuilder(II);
    if (auto *tryApply = dyn_cast<TryApplyInst>(applyInst)) {
      auto *tgtBB = tryApply->getNormalBB();
      assert(tgtBB && "Could not find try apply's target BB");
      loadBuilder.setInsertionPoint(tgtBB->begin());
    } else {
      ++II;
      loadBuilder.setInsertionPoint(II);
    }
    if (!pass.F->hasOwnership()) {
      load = loadBuilder.createLoad(applyInst->getLoc(), value,
                                    LoadOwnershipQualifier::Unqualified);
    } else {
      load = loadBuilder.createLoad(applyInst->getLoc(), value,
                                    LoadOwnershipQualifier::Take);
    }
    pass.argsToLoadedValueMap[value] = load;
    return;
  }

  assert(!ApplySite::isa(value) && "Unexpected instruction");

  // Find the first non-AllocStackInst and use its scope when creating
  // the new SILBuilder. An AllocStackInst does not directly cause any
  // code to be generated. The location of an AllocStackInst carries information
  // about the source variable; it doesn't matter where in the instruction
  // stream the AllocStackInst is located.
  auto BBIter = pass.F->begin()->begin();
  SILInstruction *FirstNonAllocStack = &*BBIter;
  while (isa<AllocStackInst>(FirstNonAllocStack) &&
         BBIter != pass.F->begin()->end()) {
    ++BBIter;
    FirstNonAllocStack = &*BBIter;
  }
  SILBuilderWithScope allocBuilder(&*pass.F->begin()->begin(),
                                   FirstNonAllocStack);

  AllocStackInst *allocInstr = allocBuilder.createAllocStack(
      RegularLocation::getAutoGeneratedLocation(), value->getType());

  LoadInst *loadCopy = nullptr;
  auto *applyOutlinedCopy =
      createOutlinedCopyCall(allocBuilder, value, allocInstr, pass);

  if (!pass.F->hasOwnership()) {
    loadCopy = allocBuilder.createLoad(applyOutlinedCopy->getLoc(), allocInstr,
                                       LoadOwnershipQualifier::Unqualified);
  } else {
    loadCopy = allocBuilder.createLoad(applyOutlinedCopy->getLoc(), allocInstr,
                                       LoadOwnershipQualifier::Take);
  }
  pass.argsToLoadedValueMap[value] = loadCopy;

  // Insert stack deallocations.
  for (TermInst *termInst : pass.returnInsts) {
    SILBuilderWithScope deallocBuilder(termInst);
    deallocBuilder.createDeallocStack(allocInstr->getLoc(), allocInstr);
  }
}

AllocStackInst *
LoadableStorageAllocation::allocateForApply(SILInstruction *apply,
                                            SILType type) {
  SILBuilderWithScope allocBuilder(&*pass.F->begin());
  SILLocation Loc = apply->getLoc();
  if (dyn_cast_or_null<VarDecl>(Loc.getAsASTNode<Decl>()))
    // FIXME: Remove this. This is likely indicative of a bug earlier in the
    // pipeline. An apply instruction should not have a VarDecl as location.
    Loc = RegularLocation::getAutoGeneratedLocation();
  auto *allocInstr = allocBuilder.createAllocStack(Loc, type);

  pass.largeLoadableArgs.push_back(allocInstr);
  pass.allocToApplyRetMap[allocInstr] = apply;
  pass.applyRetToAllocMap[apply] = allocInstr;

  for (TermInst *termInst : pass.returnInsts) {
    SILBuilderWithScope deallocBuilder(termInst);
    deallocBuilder.createDeallocStack(allocInstr->getLoc(), allocInstr);
  }

  return allocInstr;
}

//===----------------------------------------------------------------------===//
// LoadableByAddress: Top-Level Function Transform.
//===----------------------------------------------------------------------===//

namespace {
class LoadableByAddress : public SILModuleTransform {
  /// The entry point to this function transformation.
  void run() override;

  void runOnFunction(SILFunction *F);

private:
  void updateLoweredTypes(SILFunction *F);
  void recreateSingleApply(SILInstruction *applyInst,
                           SmallVectorImpl<SILInstruction *> &Delete);
  bool recreateApply(SILInstruction &I,
                     SmallVectorImpl<SILInstruction *> &Delete);
  bool recreateTupleInstr(SILInstruction &I,
                          SmallVectorImpl<SILInstruction *> &Delete);
  bool recreateConvInstr(SILInstruction &I,
                         SmallVectorImpl<SILInstruction *> &Delete);
  bool recreateBuiltinInstr(SILInstruction &I,
                         SmallVectorImpl<SILInstruction *> &Delete);
  bool recreateLoadInstr(SILInstruction &I,
                         SmallVectorImpl<SILInstruction *> &Delete);
  bool recreateUncheckedEnumDataInstr(SILInstruction &I,
                         SmallVectorImpl<SILInstruction *> &Delete);
  bool recreateUncheckedEnumDataAddrInst(SILInstruction &I,
                         SmallVectorImpl<SILInstruction *> &Delete);
  bool fixStoreToBlockStorageInstr(SILInstruction &I,
                         SmallVectorImpl<SILInstruction *> &Delete);

  bool recreateDifferentiabilityWitnessFunction(
      SILInstruction &I, SmallVectorImpl<SILInstruction *> &Delete);

  bool shouldTransformGlobal(SILGlobalVariable *global);

  bool shouldTransformInitExprOfGlobal(SILGlobalVariable *global);

private:
  llvm::SetVector<SILFunction *> modFuncs;
  llvm::SetVector<SingleValueInstruction *> conversionInstrs;
  llvm::SetVector<BuiltinInst *> builtinInstrs;
  llvm::SetVector<LoadInst *> loadInstrsOfFunc;
  llvm::SetVector<UncheckedEnumDataInst *> uncheckedEnumDataOfFunc;
  llvm::SetVector<UncheckedEnumDataAddrInstBase *>
      uncheckedEnumDataAddrOfFunc;
  llvm::SetVector<StoreInst *> storeToBlockStorageInstrs;
  llvm::SetVector<SILInstruction *> modApplies;
  llvm::MapVector<SILInstruction *, SILValue> allApplyRetToAllocMap;

public:
  LargeSILTypeMapper MapperCache;
};
} // end anonymous namespace

/// Given that we've allocated space to hold a scalar value, try to rewrite
/// the uses of the scalar to be uses of the address.
static void rewriteUsesOfScalar(StructLoweringState &pass, SILValue address,
                                SILValue scalar, StoreInst *storeToAddress) {
  // Copy the uses, since we're going to edit them.
  SmallVector<Operand *, 8> uses(scalar->getUses());
  for (Operand *scalarUse : uses) {
    SILInstruction *user = scalarUse->getUser();

    if (ApplySite::isa(user)) {
      ApplySite site(user);
      if (modifiableApply(site, pass.Mod)) {
        // Just rewrite the operand in-place.  This will produce a temporary
        // type error, but we should fix that up when we rewrite the apply's
        // function type.
        scalarUse->set(address);
      }
    } else if (isa<YieldInst>(user)) {
      // The rules for the yield are changing anyway, so we can just rewrite
      // it in-place.
      scalarUse->set(address);
    } else if (auto *storeUser = dyn_cast<StoreInst>(user)) {
      // Don't rewrite the store to the allocation.
      if (storeUser == storeToAddress)
        continue;

      // Optimization: replace with copy_addr to reduce code size
      assert(std::find(pass.storeInstsToMod.begin(), pass.storeInstsToMod.end(),
                       storeUser) == pass.storeInstsToMod.end() &&
             "Did not expect this instr in storeInstsToMod");
      SILBuilderWithScope copyBuilder(storeUser);
      SILValue dest = storeUser->getDest();
      createOutlinedCopyCall(copyBuilder, address, dest, pass);
      storeUser->eraseFromParent();
    } else if (auto *dbgInst = dyn_cast<DebugValueInst>(user)) {
      // Update the debug_value to point to the variable in the alloca.
      scalarUse->set(address);
      dbgInst->prependDeref(scalarUse->getOperandNumber());
    }
  }
}

static void allocateAndSetForInstResult(StructLoweringState &pass,
                                        SILValue instResult,
                                        SILInstruction *inst) {
  auto alloc = allocate(pass, instResult->getType());

  auto II = inst->getIterator();
  ++II;
  auto store = createStoreInit(pass, II, inst->getLoc(), instResult, alloc);

  // Traverse all the uses of instResult - see if we can replace
  rewriteUsesOfScalar(pass, alloc, instResult, store);
}

static void allocateAndSetForArgument(StructLoweringState &pass,
                                      SILArgument *value,
                                      SILInstruction *user) {
  AllocStackInst *alloc = allocate(pass, value->getType());

  SILLocation loc = user->getLoc();
  loc.markAutoGenerated();

  // Store the value into the allocation.
  auto II = value->getParent()->begin();
  if (II == alloc->getParent()->begin()) {
    // Store should happen *after* the allocation.
    ++II;
  }
  auto store = createStoreInit(pass, II, loc, value, alloc);

  // Traverse all the uses of value - see if we can replace
  rewriteUsesOfScalar(pass, alloc, value, store);
}

static bool allUsesAreReplaceable(StructLoweringState &pass,
                                  SingleValueInstruction *instr) {
  for (auto *user : instr->getUses()) {
    SILInstruction *userIns = user->getUser();
    switch (userIns->getKind()) {
    case SILInstructionKind::RetainValueInst:
    case SILInstructionKind::ReleaseValueInst:
    case SILInstructionKind::StoreInst:
    case SILInstructionKind::DebugValueInst:
    case SILInstructionKind::DestroyValueInst:
      break;
    case SILInstructionKind::ApplyInst:
    case SILInstructionKind::TryApplyInst:
    case SILInstructionKind::BeginApplyInst:
    case SILInstructionKind::PartialApplyInst: {
      // Replaceable only if it is not the function pointer
      ApplySite site(userIns);
      if (!modifiableApply(site, pass.Mod)) {
        return false;
      }
      SILValue callee = site.getCallee();
      if (callee == instr) {
        return false;
      }
      SILType currType = instr->getType().getObjectType();
      SILType newSILType = pass.getNewSILType(pass.F->getLoweredFunctionType(),
                                              currType);
      if (currType == newSILType) {
        return false;
      }
      break;
    }
    case SILInstructionKind::YieldInst:
      if (!isYieldUseRewritable(pass, cast<YieldInst>(userIns), user))
        return false;
      break;
    case SILInstructionKind::StructExtractInst:
    case SILInstructionKind::SwitchEnumInst:
      break;
    default:
      return false;
    }
  }
  return true;
}

void LoadableStorageAllocation::replaceLoad(LoadInst *load) {
  if (allUsesAreReplaceable(pass, load)) {
    replaceLoadWithCopyAddr(load);
  } else {
    replaceLoadWithCopyAddrForModifiable(load);
  }
}

static void allocateAndSet(StructLoweringState &pass,
                           LoadableStorageAllocation &allocator,
                           SILValue operand, SILInstruction *user,
                           Operand &opd) {
  if (isa<SILUndef>(operand)) {
    auto alloc = allocate(pass, operand->getType());
    opd.set(alloc);
    return;
  }

  auto inst = operand->getDefiningInstruction();
  if (!inst) {
    allocateAndSetForArgument(pass, cast<SILArgument>(operand), user);
    return;
  }

  if (auto *load = dyn_cast<LoadInst>(operand)) {
    allocator.replaceLoad(load);
  } else {
    // TODO: peephole: special handling of known cases:
    // ApplyInst, TupleExtractInst
    allocateAndSetForInstResult(pass, operand, inst);
  }
}

/// Rewrite all of the large-loadable operands in the given list.
static void allocateAndSetAll(StructLoweringState &pass,
                              LoadableStorageAllocation &allocator,
                              SILInstruction *user,
                              MutableArrayRef<Operand> operands) {
  PrettyStackTraceSILNode backtrace("Running allocateAndSetAll on ", user);

  for (Operand &operand : operands) {
    SILValue value = operand.get();
    SILType silType = value->getType();
    if (pass.isLargeLoadableType(pass.F->getLoweredFunctionType(),
                                 silType)) {
      allocateAndSet(pass, allocator, value, user, operand);
    }
  }
}

static void retypeTupleInstr(SingleValueInstruction *instr, IRGenModule &Mod,
                             LargeSILTypeMapper &Mapper) {
  SILType currSILType = instr->getType();
  auto funcType = getInnerFunctionType(currSILType);
  assert(funcType && "Expected a function Type");
  GenericEnvironment *genEnv = getSubstGenericEnvironment(instr->getFunction());
  if (!genEnv && funcType->getSubstGenericSignature()) {
    genEnv = getSubstGenericEnvironment(funcType);
  }
  SILType newSILType = Mapper.getNewSILType(genEnv, currSILType, Mod);
  if (currSILType == newSILType) {
    return;
  }

  auto II = instr->getIterator();
  ++II;
  SILBuilderWithScope Builder(II);
  SingleValueInstruction *newInstr = nullptr;
  switch (instr->getKind()) {
  // Add cast to the new sil function type:
  case SILInstructionKind::TupleExtractInst: {
    auto extractInst = cast<TupleExtractInst>(instr);
    newInstr = Builder.createTupleExtract(
      extractInst->getLoc(), extractInst->getOperand(),
      extractInst->getFieldIndex(),
      newSILType.getObjectType());
    break;
  }
  case SILInstructionKind::TupleElementAddrInst: {
    auto elementAddrInst = cast<TupleElementAddrInst>(instr);
    newInstr = Builder.createTupleElementAddr(
      elementAddrInst->getLoc(), elementAddrInst->getOperand(),
      elementAddrInst->getFieldIndex(),
      newSILType.getAddressType());
    break;
  }
  default:
    llvm_unreachable("Unexpected instruction inside tupleInstsToMod");
  }
  instr->replaceAllUsesWith(newInstr);
  instr->eraseFromParent();
}

static SILValue createCopyOfEnum(StructLoweringState &pass,
                                 SwitchEnumInst *orig) {
  auto value = orig->getOperand();
  auto type = value->getType();
  if (type.isObject()) {
    // support for non-address operands / enums
    auto *alloc = allocate(pass, type);
    createStoreInit(pass, orig->getIterator(), orig->getLoc(), value, alloc);
    return alloc;
  }

  auto alloc = allocate(pass, type.getObjectType());

  SILBuilderWithScope copyBuilder(orig);
  createOutlinedCopyCall(copyBuilder, value, alloc, pass);

  return alloc;
}

static void createStructElementAddrInstrAndLoad(LoadableStorageAllocation &allocator,
                                       StructExtractInst *instr,
                                       StructLoweringState &pass) {
  bool updateResultTy = pass.resultTyInstsToMod.count(instr) != 0;
  if (updateResultTy) {
    pass.resultTyInstsToMod.remove(instr);
  }
  SILBuilderWithScope builder(instr);
  SingleValueInstruction *newInstr = builder.createStructElementAddr(
      instr->getLoc(), instr->getOperand(),
      instr->getField(),
      instr->getType().getAddressType());
  // Load the struct element then see if we can get rid of the load:
  LoadInst *loadArg = nullptr;
  if (!pass.F->hasOwnership()) {
    loadArg = builder.createLoad(newInstr->getLoc(), newInstr,
                                 LoadOwnershipQualifier::Unqualified);
  } else {
    loadArg = builder.createLoad(newInstr->getLoc(), newInstr,
                                 LoadOwnershipQualifier::Take);
  }
  instr->replaceAllUsesWith(loadArg);
  instr->getParent()->erase(instr);

  // If the load is of a function type - do not replace it.
  if (isFuncOrOptionalFuncType(loadArg->getType())) {
    return;
  }

  allocator.replaceLoad(loadArg);

  if (updateResultTy) {
    pass.resultTyInstsToMod.insert(newInstr);
  }
}

static SILValue getMakeAddrBorrowOperand(SILBuilder &builder,
                                         MakeBorrowInst *orig) {
  // This doesn't support OSSA yet. That isn't typically a problem since this
  // pass currently only runs during IRGen preparation.
  auto &fn = builder.getFunction();
  assert(!fn.hasOwnership());

  auto loc = orig->getLoc();
  auto operand = orig->getOperand();
  
  // If the operand was already spilled to an address by the pass, then use
  // it as is.
  if (operand->getType().isAddress()) {
    return operand;
  }

  // If the operand is a load or load_borrow of a memory location, borrow that
  // original address.
  if (auto load = dyn_cast<LoadInst>(operand)) {
    return load->getOperand();
  }
  if (auto lb = dyn_cast<LoadBorrowInst>(operand)) {
    return lb->getOperand();
  }

  // Otherwise, this value is local, and can be spilled to a local stack
  // allocation.
  AllocStackInst *stack;
  {
    SavedInsertionPointRAII save(builder);

    SmallVector<SILValue, 4> typeDependentOperands;
    if (operand->getType().hasLocalArchetype()) {
      operand->getType().getASTType().visit([&](CanType t) {
          if (auto localArchetype = dyn_cast<LocalArchetypeType>(t)) {
            typeDependentOperands.push_back(
                  fn.getModule().getRootLocalArchetypeDef(localArchetype, &fn));
          }
        });
    }

    // If the operand is not type-dependent at all, then we can insert the stack
    // allocation in the entry block.
    if (typeDependentOperands.empty()) {
      builder.setInsertionPoint(&*fn.begin()->begin());
    }
    // Otherwise, find the earliest block dominated by all of the dependent
    // operands.
    else {
      auto insertPoint = typeDependentOperands.front()->getNextInstruction();

      // TODO: If there are multiple type dependencies, find the point at
      // which all of the type dependent operands join together.
      builder.setInsertionPoint(insertPoint);
    }
    stack = builder.createAllocStack(loc, operand->getType());
  }
  
  builder.createStore(loc, operand, stack, StoreOwnershipQualifier::Unqualified);

  // Insert balancing dealloc_stacks.
  DominanceInfo domTree(&fn);
  SmallVector<SILBasicBlock *, 4> domBoundary;
  computeDominatedBoundaryBlocks(stack->getParentBlock(), &domTree, domBoundary);

  for (auto *boundBlock : domBoundary) {
    SavedInsertionPointRAII save(builder);

    builder.setInsertionPoint(boundBlock->getTerminator());
    builder.createDeallocStack(loc, stack);
  }

  StackNesting::fixNesting(&fn);
  
  return stack;
}

static void createMakeAddrBorrow(LoadableStorageAllocation &allocator,
                                 MakeBorrowInst *instr,
                                 StructLoweringState &pass) {
  bool updateResultTy = pass.resultTyInstsToMod.count(instr) != 0;
  if (updateResultTy) {
    pass.resultTyInstsToMod.remove(instr);
  }

  SILBuilderWithScope builder(instr);
  auto operand = getMakeAddrBorrowOperand(builder, instr);
  SingleValueInstruction *newInstr = builder.createMakeAddrBorrow(
      instr->getLoc(), operand);

  // `make_addr_borrow` produces a `Borrow<T>` by-value, like `make_borrow`,
  // so it is a drop-in replacement.
  instr->replaceAllUsesWith(newInstr);
  instr->getParent()->erase(instr);

  if (updateResultTy) {
    pass.resultTyInstsToMod.insert(newInstr);
  }
}

static void
createDereferenceAddrBorrowAndLoad(LoadableStorageAllocation &allocator,
                                   DereferenceBorrowInst *instr,
                                   StructLoweringState &pass) {
  bool updateResultTy = pass.resultTyInstsToMod.count(instr) != 0;
  if (updateResultTy) {
    pass.resultTyInstsToMod.remove(instr);
  }

  SILBuilderWithScope builder(instr);
  auto *newInstr = builder.createDereferenceAddrBorrow(
      instr->getLoc(), instr->getOperand());
  
  // Load the borrowed value and then see if we can get rid of the load.
  SingleValueInstruction *loadArg;
  if (!pass.F->hasOwnership()) {
    loadArg = builder.createLoad(newInstr->getLoc(), newInstr,
                                 LoadOwnershipQualifier::Unqualified);
  } else {
    loadArg = builder.createLoadBorrow(newInstr->getLoc(), newInstr);
  }
  instr->replaceAllUsesWith(loadArg);
  instr->getParent()->erase(instr);

  // TODO: Try to eliminate the load_borrow
  
  if (updateResultTy) {
    pass.resultTyInstsToMod.insert(newInstr);
  }
}

static void rewriteFunction(StructLoweringState &pass,
                            LoadableStorageAllocation &allocator) {

  bool repeat = false;
  llvm::SetVector<SILInstruction *> currentModApplies;
  do {
    while (!pass.switchEnumInstsToMod.empty()) {
      auto *instr = pass.switchEnumInstsToMod.pop_back_val();
      /* unchecked_take_enum_data_addr can be destructive.
       * work on a copy instead of the original enum */
      auto copiedValue = createCopyOfEnum(pass, instr);
      SILBuilderWithScope enumBuilder(instr);
      unsigned numOfCases = instr->getNumCases();
      SmallVector<std::pair<EnumElementDecl *, SILBasicBlock *>, 16> caseBBs;
      for (unsigned i = 0; i < numOfCases; ++i) {
        auto currCase = instr->getCase(i);
        auto *currBB = currCase.second;
        SILBuilderWithScope argBuilder(currBB->begin());
        assert(currBB->getNumArguments() <= 1 && "Unhandled BB Type");
        EnumElementDecl *decl = currCase.first;
        for (SILArgument *arg : currBB->getArguments()) {
          SILType storageType = arg->getType();
          SILType newSILType = pass.getNewSILType(pass.F->getLoweredFunctionType(),
                                                  storageType);
          if (storageType == newSILType) {
            newSILType = newSILType.getAddressType();
          }

          auto *newArg = argBuilder.createUncheckedEnumDataAddrForTake(
              instr->getLoc(), copiedValue, decl, newSILType.getAddressType());
          arg->replaceAllUsesWith(newArg);
          currBB->eraseArgument(0);

          // Load the enum addr then see if we can get rid of the load:
          LoadInst *loadArg = nullptr;
          if (!pass.F->hasOwnership()) {
            loadArg = argBuilder.createLoad(
                newArg->getLoc(), newArg, LoadOwnershipQualifier::Unqualified);
          } else {
            loadArg = argBuilder.createLoad(newArg->getLoc(), newArg,
                                            LoadOwnershipQualifier::Take);
          }
          newArg->replaceAllUsesWith(loadArg);
          loadArg->setOperand(newArg);

          // If the load is of a function type - do not replace it.
          if (isFuncOrOptionalFuncType(loadArg->getType())) {
            continue;
          }

          allocator.replaceLoad(loadArg);
        }
        caseBBs.push_back(std::make_pair(decl, currBB));
      }
      SILBasicBlock *defaultBB =
          instr->hasDefault() ? instr->getDefaultBB() : nullptr;
      enumBuilder.createSwitchEnumAddr(
          instr->getLoc(), copiedValue, defaultBB, caseBBs);
      instr->getParent()->erase(instr);
    }

    while (!pass.structExtractInstsToMod.empty()) {
      auto *instr = pass.structExtractInstsToMod.pop_back_val();
      createStructElementAddrInstrAndLoad(allocator, instr, pass);
    }

    while (!pass.makeBorrowInstsToMod.empty()) {
      auto *instr = pass.makeBorrowInstsToMod.pop_back_val();
      createMakeAddrBorrow(allocator, instr, pass);
    }

    while (!pass.dereferenceBorrowInstsToMod.empty()) {
      auto *instr = pass.dereferenceBorrowInstsToMod.pop_back_val();
      createDereferenceAddrBorrowAndLoad(allocator, instr, pass);
    }

    while (!pass.applies.empty()) {
      auto *applyInst = pass.applies.pop_back_val();
      if (currentModApplies.count(applyInst) == 0) {
        currentModApplies.insert(applyInst);
      }
      ApplySite applySite = ApplySite(applyInst);
      allocateAndSetAll(pass, allocator, applyInst,
                        applySite.getArgumentOperands());
    }

    while (!pass.modYieldInsts.empty()) {
      YieldInst *inst = pass.modYieldInsts.pop_back_val();
      allocateAndSetAll(pass, allocator, inst, inst->getAllOperands());
    }

    repeat = !pass.switchEnumInstsToMod.empty() ||
             !pass.structExtractInstsToMod.empty();
    assert(pass.applies.empty());
    pass.applies.append(currentModApplies.begin(), currentModApplies.end());
  } while (repeat);

  for (SILInstruction *instr : pass.instsToMod) {
    for (Operand &operand : instr->getAllOperands()) {
      auto currOperand = operand.get();
      if (std::find(pass.largeLoadableArgs.begin(),
                    pass.largeLoadableArgs.end(),
                    currOperand) != pass.largeLoadableArgs.end()) {
        SILValue newOperand = pass.argsToLoadedValueMap[currOperand];
        assert(newOperand != currOperand &&
               "Did not allocate storage and convert operand");
        operand.set(newOperand);
      }
    }
  }

  for (SingleValueInstruction *instr : pass.tupleInstsToMod) {
    retypeTupleInstr(instr, pass.Mod, pass.Mapper);
  }

  while (!pass.allocStackInstsToMod.empty()) {
    auto *instr = pass.allocStackInstsToMod.pop_back_val();
    SILBuilderWithScope allocBuilder(instr);
    SILType currSILType = instr->getType();
    SILType newSILType = pass.getNewSILType(pass.F->getLoweredFunctionType(),
                                            currSILType);
    auto varInfo = instr->getVarInfo();
    // Update the debug variable type to match the new SSA type so the
    // SIL verifier does not see a mismatch between the two.
    if (varInfo && varInfo->Type)
      varInfo->Type = newSILType.getObjectType();
    auto *newInstr =
        allocBuilder.createAllocStack(instr->getLoc(), newSILType, varInfo);
    instr->replaceAllUsesWith(newInstr);
    instr->getParent()->erase(instr);
  }

  while (!pass.pointerToAddrkInstsToMod.empty()) {
    auto *instr = pass.pointerToAddrkInstsToMod.pop_back_val();
    SILBuilderWithScope pointerBuilder(instr);
    SILType currSILType = instr->getType();
    SILType newSILType = pass.getNewSILType(pass.F->getLoweredFunctionType(),
                                            currSILType);
    auto *newInstr = pointerBuilder.createPointerToAddress(
        instr->getLoc(), instr->getOperand(), newSILType.getAddressType(),
        instr->isStrict(), instr->isInvariant(), instr->alignment());
    instr->replaceAllUsesWith(newInstr);
    instr->getParent()->erase(instr);
  }

  for (Operand *operand : pass.debugUsesToMod) {
    auto *instr = cast<DebugValueInst>(operand->getUser());
    auto currOperand = operand->get();
    if (pass.argsToLoadedValueMap.find(currOperand) !=
        pass.argsToLoadedValueMap.end()) {
      SILValue newOperand = pass.argsToLoadedValueMap[currOperand];
      assert(newOperand != currOperand &&
             "Did not allocate storage and convert operand");
      operand->set(newOperand);
    } else {
      assert(currOperand->getType().isAddress() &&
             "Expected an address type");
      instr->prependDeref(operand->getOperandNumber());
    }
  }

  for (SILInstruction *instr : pass.destroyValueInstsToMod) {
    assert(instr->getAllOperands().size() == 1 &&
           "destroy_value instructions have one operand");
    for (Operand &operand : instr->getAllOperands()) {
      auto currOperand = operand.get();
      assert(currOperand->getType().isAddress() && "Expected an address type");
      SILBuilderWithScope destroyBuilder(instr);
      destroyBuilder.createDestroyAddr(instr->getLoc(), currOperand);
      instr->getParent()->erase(instr);
    }
  }

  for (StoreInst *instr : pass.storeInstsToMod) {
    SILValue src = instr->getSrc();
    SILValue tgt = instr->getDest();
    SILType srcType = src->getType();
    SILType tgtType = tgt->getType();
    assert(srcType && "Expected an address-type source");
    assert(tgtType.isAddress() && "Expected an address-type target");
    assert(srcType == tgtType && "Source and target type do not match");
    (void)srcType;
    (void)tgtType;

    SILBuilderWithScope copyBuilder(instr);
    createOutlinedCopyCall(copyBuilder, src, tgt, pass);
    instr->getParent()->erase(instr);
  }

  for (RetainValueInst *instr : pass.retainInstsToMod) {
    SILBuilderWithScope retainBuilder(instr);
    retainBuilder.createRetainValueAddr(
        instr->getLoc(), instr->getOperand(), instr->getAtomicity());
    instr->getParent()->erase(instr);
  }

  for (ReleaseValueInst *instr : pass.releaseInstsToMod) {
    SILBuilderWithScope releaseBuilder(instr);
    releaseBuilder.createReleaseValueAddr(
        instr->getLoc(), instr->getOperand(), instr->getAtomicity());
    instr->getParent()->erase(instr);
  }

  for (SingleValueInstruction *instr : pass.resultTyInstsToMod) {
    // Update the return type of these instrs
    // Note: The operand was already updated!
    SILType currSILType = instr->getType().getObjectType();
    SILType newSILType = pass.getNewSILType(pass.F->getLoweredFunctionType(),
                                            currSILType);
    SILBuilderWithScope resultTyBuilder(instr);
    SILLocation Loc = instr->getLoc();
    SingleValueInstruction *newInstr = nullptr;
    switch (instr->getKind()) {
    case SILInstructionKind::StructExtractInst: {
      auto *convInstr = cast<StructExtractInst>(instr);
      newInstr = resultTyBuilder.createStructExtract(
          Loc, convInstr->getOperand(), convInstr->getField(),
          newSILType.getObjectType());
      break;
    }
    case SILInstructionKind::StructElementAddrInst: {
      auto *convInstr = cast<StructElementAddrInst>(instr);
      newInstr = resultTyBuilder.createStructElementAddr(
          Loc, convInstr->getOperand(), convInstr->getField(),
          newSILType.getAddressType());
      break;
    }
    case SILInstructionKind::UncheckedTakeEnumDataAddrInst: {
      auto *convInstr = cast<UncheckedTakeEnumDataAddrInst>(instr);
      newInstr = resultTyBuilder.createUncheckedTakeEnumDataAddr(
          Loc, convInstr->getOperand(), convInstr->getElement(),
          newSILType.getAddressType());
      break;
    }
    case SILInstructionKind::UncheckedInPlaceEnumDataAddrInst: {
      auto *convInstr = cast<UncheckedInPlaceEnumDataAddrInst>(instr);
      newInstr = resultTyBuilder.createUncheckedInPlaceEnumDataAddr(
          Loc, convInstr->getOperand(), convInstr->getElement(),
          newSILType.getAddressType());
      break;
    }
    case SILInstructionKind::UncheckedBorrowEnumDataAddrInst: {
      auto *convInstr = cast<UncheckedBorrowEnumDataAddrInst>(instr);
      newInstr = resultTyBuilder.createUncheckedBorrowEnumDataAddr(
          Loc, convInstr->getEnum(), convInstr->getScratch(),
          convInstr->getElement(),
          newSILType.getAddressType());
      break;
    }
    case SILInstructionKind::InitEnumDataAddrInst: {
      auto *convInstr = cast<InitEnumDataAddrInst>(instr);
      newInstr = resultTyBuilder.createInitEnumDataAddr(
          Loc, convInstr->getOperand(), convInstr->getElement(),
          newSILType.getAddressType());
      break;
    }
    case SILInstructionKind::RefTailAddrInst: {
      auto *convInstr = cast<RefTailAddrInst>(instr);
      newInstr = resultTyBuilder.createRefTailAddr(Loc, convInstr->getOperand(),
                                                   newSILType.getAddressType());
      break;
    }
    case SILInstructionKind::RefElementAddrInst: {
      auto *convInstr = cast<RefElementAddrInst>(instr);
      newInstr = resultTyBuilder.createRefElementAddr(
          Loc, convInstr->getOperand(), convInstr->getField(),
          newSILType.getAddressType());
      break;
    }
    case SILInstructionKind::BeginAccessInst: {
      auto *convInstr = cast<BeginAccessInst>(instr);
      newInstr = resultTyBuilder.createBeginAccess(
          Loc, convInstr->getOperand(), convInstr->getAccessKind(),
          convInstr->getEnforcement(), convInstr->hasNoNestedConflict(),
          convInstr->isFromBuiltin());
      break;
    }
    case SILInstructionKind::EnumInst: {
      auto *convInstr = cast<EnumInst>(instr);
      SILValue operand =
          convInstr->hasOperand() ? convInstr->getOperand() : SILValue();
      newInstr = resultTyBuilder.createEnum(
          Loc, operand, convInstr->getElement(), newSILType.getObjectType());
      break;
    }
    case SILInstructionKind::DereferenceBorrowAddrInst: {
      auto dbaInstr = cast<DereferenceBorrowAddrInst>(instr);
      newInstr = resultTyBuilder.createDereferenceBorrowAddr(
          Loc, dbaInstr->getOperand());
      break;
    }
    default:
      llvm_unreachable("Unhandled aggrTy instr");
    }
    instr->replaceAllUsesWith(newInstr);
    instr->eraseFromParent();
  }

  for (MethodInst *instr : pass.methodInstsToMod) {
    SILType currSILType = instr->getType();
    auto currSILFunctionType = currSILType.castTo<SILFunctionType>();
    GenericEnvironment *genEnvForMethod = nullptr;
    if (currSILFunctionType->isPolymorphic()) {
      genEnvForMethod = getSubstGenericEnvironment(currSILFunctionType);
    }
    SILType newSILType =
        SILType::getPrimitiveObjectType(pass.Mapper.getNewSILFunctionType(
            genEnvForMethod, currSILFunctionType, pass.Mod));
    auto member = instr->getMember();
    auto loc = instr->getLoc();
    SILBuilderWithScope methodBuilder(instr);
    MethodInst *newInstr = nullptr;

    switch (instr->getKind()) {
    case SILInstructionKind::ClassMethodInst: {
      SILValue selfValue = instr->getOperand(0);
      newInstr = methodBuilder.createClassMethod(loc, selfValue, member,
                                                 newSILType);
      break;
    }
    case SILInstructionKind::SuperMethodInst: {
      SILValue selfValue = instr->getOperand(0);
      newInstr = methodBuilder.createSuperMethod(loc, selfValue, member,
                                                 newSILType);
      break;
    }
    case SILInstructionKind::WitnessMethodInst: {
      auto *WMI = cast<WitnessMethodInst>(instr);
      newInstr = methodBuilder.createWitnessMethod(
          loc, WMI->getLookupType(), WMI->getConformance(), member, newSILType);
      break;
    }
    default:
      llvm_unreachable("Expected known MethodInst ValueKind");
    }

    instr->replaceAllUsesWith(newInstr);
    instr->getParent()->erase(instr);
  }

  while (!pass.modReturnInsts.empty()) {
    auto *instr = pass.modReturnInsts.pop_back_val();
    auto loc = instr->getLoc(); // SILLocation::RegularKind
    auto regLoc = RegularLocation(loc);
    SILBuilderWithScope retBuilder(instr);
    assert(modNonFuncTypeResultType(pass.F, pass.Mod) &&
           "Expected a regular type");

    auto retOp = instr->getOperand();

    if (pass.F->getConventionsInContext().hasGuaranteedResult()) {
      auto *load = dyn_cast<LoadInst>(retOp);
      if (load) {
        auto loadOperand = load->getOperand();
        retBuilder.createReturn(regLoc, loadOperand);
        instr->eraseFromParent();

        // Delete the now-unused load instruction
        if (load->use_empty()) {
          load->eraseFromParent();
        }
      }

      continue;
    }
    // Before we return an empty tuple, init return arg:
    auto storageType = retOp->getType();
    auto *entry = pass.F->getEntryBlock();
    auto *retArg = entry->getArgument(0);

    if (storageType.isAddress()) {
      // There *might* be a dealloc_stack that already released this value
      // we should create the copy *before* the epilogue's deallocations
      auto IIR = instr->getReverseIterator();
      for (++IIR; IIR != instr->getParent()->rend(); ++IIR) {
        auto *currIIInstr = &(*IIR);
        if (currIIInstr->getKind() != SILInstructionKind::DeallocStackInst) {
          // got the right location - stop.
          --IIR;
          break;
        }
      }
      auto II = (IIR != instr->getParent()->rend())
                    ? IIR->getIterator()
                    : instr->getParent()->begin();
      SILBuilderWithScope retCopyBuilder(II);
      createOutlinedCopyCall(retCopyBuilder, retOp, retArg, pass, &regLoc);
    } else {
      retBuilder.createStore(regLoc, retOp, retArg,
                             getStoreInitOwnership(pass, retOp->getType()));
    }
    auto emptyTy = SILType::getPrimitiveObjectType(
        retBuilder.getModule().getASTContext().TheEmptyTupleType);
    auto newRetTuple = retBuilder.createTuple(regLoc, emptyTy, {});
    retBuilder.createReturn(newRetTuple->getLoc(), newRetTuple);
    instr->eraseFromParent();
  }
}

// Rewrite function return argument if it is a "function pointer"
// If it is a large type also return true - will be re-written later
// Returns true if the return argument needed re-writing
static bool rewriteFunctionReturn(StructLoweringState &pass) {
  auto loweredTy = pass.F->getLoweredFunctionType();
  SILFunction *F = pass.F;
  SILType resultTy = loweredTy->getAllResultsInterfaceType();
  SILType newSILType = pass.getNewSILType(loweredTy, resultTy);
  // We (currently) only care about function signatures
  if (pass.isLargeLoadableType(loweredTy, resultTy)) {
    return true;
  } else if (pass.containsDifferentFunctionSignature(loweredTy, resultTy)) {
    llvm::SmallVector<SILResultInfo, 2> newSILResultInfo;
    if (auto tupleType = newSILType.getAs<TupleType>()) {
      auto originalResults = loweredTy->getResults();
      for (unsigned int i = 0; i < originalResults.size(); ++i) {
        auto origResultInfo = originalResults[i];
        auto canElem = tupleType.getElementType(i);
        SILType objectType = SILType::getPrimitiveObjectType(canElem);
        auto newResult = SILResultInfo(objectType.getASTType(), origResultInfo.getConvention());
        newSILResultInfo.push_back(newResult);
      }
    } else {
      assert(loweredTy->getNumResults() == 1 && "Expected a single result");
      auto origResultInfo = loweredTy->getSingleResult();
      auto newResult = SILResultInfo(newSILType.getASTType(), origResultInfo.getConvention());
      newSILResultInfo.push_back(newResult);
    }

    auto NewTy = SILFunctionType::get(
        loweredTy->getInvocationGenericSignature(),
        loweredTy->getExtInfo(),
        loweredTy->getCoroutineKind(),
        loweredTy->getCalleeConvention(),
        loweredTy->getParameters(),
        loweredTy->getYields(),
        newSILResultInfo, loweredTy->getOptionalErrorResult(),
        loweredTy->getPatternSubstitutions(),
        loweredTy->getInvocationSubstitutions(),
        F->getModule().getASTContext(),
        loweredTy->getWitnessMethodConformanceOrInvalid());
    F->rewriteLoweredTypeUnsafe(NewTy);
    return true;
  }
  return false;
}

void LoadableByAddress::runOnFunction(SILFunction *F) {
  PrettyStackTraceSILFunction backtrace("Running LoadableByAddress on ", F);

  CanSILFunctionType funcType = F->getLoweredFunctionType();
  IRGenModule *currIRMod = getIRGenModule()->IRGen.getGenModule(F);

  if (F->isExternalDeclaration()) {
    if (!modifiableFunction(funcType)) {
      return;
    }
    // External function - re-write external declaration - this is ABI!
    GenericEnvironment *genEnv = getSubstGenericEnvironment(F);
    auto loweredTy = F->getLoweredFunctionType();
    if (!genEnv && loweredTy->getSubstGenericSignature()) {
      genEnv = getSubstGenericEnvironment(loweredTy);
    }
    if (MapperCache.shouldTransformFunctionType(genEnv, loweredTy,
                                                *currIRMod)) {
      modFuncs.insert(F);
    }
    return;
  }
  
  StructLoweringState pass(F, *currIRMod, MapperCache);

  // Rewrite function args and insert allocs.
  LoadableStorageAllocation allocator(pass);
  allocator.allocateLoadableStorage();

  bool rewrittenReturn = false;
  if (modifiableFunction(funcType)) {
    rewrittenReturn = rewriteFunctionReturn(pass);
  }

  LLVM_DEBUG(llvm::dbgs() << "\nREWRITING: " << F->getName();
             F->print(llvm::dbgs()));

  // Rewrite instructions relating to the loadable struct.
  rewriteFunction(pass, allocator);

  invalidateAnalysis(F, SILAnalysis::InvalidationKind::Instructions);

  // If we modified the function arguments - add to list of functions to clone
  if (modifiableFunction(funcType) &&
      (rewrittenReturn ||
       !pass.largeLoadableArgs.empty() ||
       !pass.funcSigArgs.empty() ||
       pass.hasLargeLoadableYields())) {
    modFuncs.insert(F);
  }
  // If we modified any applies - add them to the global list for recreation
  if (!pass.applies.empty()) {
    modApplies.insert(pass.applies.begin(), pass.applies.end());
  }
  if (!pass.applyRetToAllocMap.empty()) {
    for (auto elm : pass.applyRetToAllocMap) {
      allApplyRetToAllocMap.insert(elm);
    }
  }
}

static SILValue
getOperandTypeWithCastIfNecessary(SILInstruction *containingInstr, SILValue op,
                                  IRGenModule &Mod, SILBuilder &builder,
                                  LargeSILTypeMapper &Mapper) {
  SILType currSILType = op->getType();
  SILType nonOptionalType = currSILType;
  if (auto optType = currSILType.getOptionalObjectType()) {
    nonOptionalType = optType;
  }
  if (auto funcType = nonOptionalType.getAs<SILFunctionType>()) {
    GenericEnvironment *genEnv =
      getSubstGenericEnvironment(containingInstr->getFunction());
    if (!genEnv && funcType->isPolymorphic()) {
      genEnv = getSubstGenericEnvironment(funcType);
    }
    auto newFnType = Mapper.getNewSILFunctionType(genEnv, funcType, Mod);
    SILType newSILType = SILType::getPrimitiveObjectType(newFnType);
    if (nonOptionalType.isAddress()) {
      newSILType = newSILType.getAddressType();
    }
    if (nonOptionalType != currSILType) {
      newSILType = SILType::getOptionalType(newSILType);
    }
    if (currSILType.isAddress()) {
      newSILType = newSILType.getAddressType();
    }
    if (currSILType.isAddress()) {
      if (newSILType != currSILType) {
        auto castInstr = builder.createUncheckedAddrCast(
            containingInstr->getLoc(), op, newSILType);
        return castInstr;
      }
      return op;
    }
    assert(currSILType.isObject() && "Expected an object type");
    if (newSILType != currSILType) {
      auto castInstr = builder.createUncheckedReinterpretCast(
          containingInstr->getLoc(), op, newSILType);
      return castInstr;
    }
  }
  return op;
}

void LoadableByAddress::recreateSingleApply(
    SILInstruction *applyInst, SmallVectorImpl<SILInstruction *> &Delete) {
  auto *F = applyInst->getFunction();
  IRGenModule *currIRMod = getIRGenModule()->IRGen.getGenModule(F);
  // Collect common info
  ApplySite applySite = ApplySite(applyInst);
  SILValue callee = applySite.getCallee();
  if (auto site = ApplySite::isa(callee)) {
    // We need to re-create the callee's apply before recreating this one
    // else verification will fail with wrong SubstCalleeType
    auto calleInstr = site.getInstruction();
    if (modApplies.remove(calleInstr)) {
      recreateSingleApply(calleInstr, Delete);
      callee = applySite.getCallee();
    }
  }
  CanSILFunctionType origSILFunctionType = applySite.getSubstCalleeType();
  GenericEnvironment *genEnv = getSubstGenericEnvironment(origSILFunctionType);
  CanSILFunctionType newSILFunctionType = MapperCache.getNewSILFunctionType(
      genEnv, origSILFunctionType, *currIRMod);
  SmallVector<SILValue, 8> callArgs;
  SILBuilderWithScope applyBuilder(applyInst);
  // If we turned a direct result into an indirect parameter
  // Find the new alloc we created earlier.
  // and pass it as first parameter:
  if ((isa<ApplyInst>(applyInst) || isa<TryApplyInst>(applyInst)) &&
      modNonFuncTypeResultType(genEnv, origSILFunctionType, *currIRMod) &&
      modifiableApply(applySite, *getIRGenModule()) &&
      !origSILFunctionType->hasGuaranteedResult(true)) {
    assert(allApplyRetToAllocMap.find(applyInst) !=
           allApplyRetToAllocMap.end());
    auto newAlloc = allApplyRetToAllocMap.find(applyInst)->second;
    callArgs.push_back(newAlloc);
  }

  // Collect arg operands
  for (Operand &operand : applySite.getArgumentOperands()) {
    SILValue currOperand = operand.get();
    currOperand = getOperandTypeWithCastIfNecessary(
        applyInst, currOperand, *currIRMod, applyBuilder, MapperCache);
    callArgs.push_back(currOperand);
  }
  // Recreate apply with new operands due to substitution-type cache
  switch (applyInst->getKind()) {
  case SILInstructionKind::ApplyInst: {
    auto *castedApply = cast<ApplyInst>(applyInst);
    SILValue newApply =
      applyBuilder.createApply(castedApply->getLoc(), callee,
                               applySite.getSubstitutionMap(),
                               callArgs,
                               castedApply->getApplyOptions());
    castedApply->replaceAllUsesWith(newApply);

    // For guaranteed address results, replace the pre-allocated stack slot
    // with the returned address
    if (modNonFuncTypeResultType(genEnv, origSILFunctionType, *currIRMod)) {
      auto allocIt = allApplyRetToAllocMap.find(applyInst);
      ASSERT(allocIt != allApplyRetToAllocMap.end());

      if (newSILFunctionType->hasGuaranteedAddressResult()) {
        // Delete all dealloc_stack instructions for this allocation
        auto *stackAlloc = cast<AllocStackInst>(allocIt->second);
        for (auto *use : stackAlloc->getUses()) {
          if (auto *dealloc = dyn_cast<DeallocStackInst>(use->getUser())) {
            dealloc->eraseFromParent();
          }
        }
        stackAlloc->replaceAllUsesWith(newApply);
        stackAlloc->eraseFromParent();
      }
    }

    break;
  }
  case SILInstructionKind::TryApplyInst: {
    auto *castedApply = cast<TryApplyInst>(applyInst);
    applyBuilder.createTryApply(
        castedApply->getLoc(), callee, applySite.getSubstitutionMap(), callArgs,
        castedApply->getNormalBB(), castedApply->getErrorBB(),
        castedApply->getApplyOptions());

    break;
  }
  case SILInstructionKind::BeginApplyInst: {
    auto oldApply = cast<BeginApplyInst>(applyInst);
    auto newApply =
      applyBuilder.createBeginApply(oldApply->getLoc(), callee,
                                    applySite.getSubstitutionMap(), callArgs,
                                    oldApply->getApplyOptions());

    // Use the new token result.
    oldApply->getTokenResult()->replaceAllUsesWith(newApply->getTokenResult());

    if (auto *allocation = oldApply->getCalleeAllocationResult()) {
      allocation->replaceAllUsesWith(newApply->getCalleeAllocationResult());
    }

    // Rewrite all the yields.
    auto oldYields = oldApply->getOrigCalleeType()->getYields();
    auto oldYieldedValues = oldApply->getYieldedValues();
    auto newYields = newApply->getOrigCalleeType()->getYields();
    auto newYieldedValues = newApply->getYieldedValues();
    assert(oldYields.size() == newYields.size() &&
           oldYields.size() == oldYieldedValues.size() &&
           newYields.size() == newYieldedValues.size());
    (void)newYields;
    for (auto i : indices(oldYields)) {
      SILValue oldValue = oldYieldedValues[i];
      SILValue newValue = newYieldedValues[i];

      // For now, just replace the value with an immediate load if the old value
      // was direct.
      if (oldValue->getType() != newValue->getType() &&
          !oldValue->getType().isAddress()) {
        LoadOwnershipQualifier ownership;
        if (!F->hasOwnership()) {
          ownership = LoadOwnershipQualifier::Unqualified;
        } else if (newValue->getType().isTrivial(*F)) {
          ownership = LoadOwnershipQualifier::Trivial;
        } else {
          assert(oldYields[i].isConsumedInCaller() &&
                 "borrowed yields not yet supported here");
          ownership = LoadOwnershipQualifier::Take;
        }
        newValue = applyBuilder.createLoad(applyInst->getLoc(), newValue,
                                           ownership);
      }
      oldValue->replaceAllUsesWith(newValue);
    }
    break;
  }
  case SILInstructionKind::PartialApplyInst: {
    auto *castedApply = cast<PartialApplyInst>(applyInst);
    // Change the type of the Closure
    auto partialApplyConvention = castedApply->getCalleeConvention();
    auto resultIsolation = castedApply->getResultIsolation();
    // We do need to update the closure's funtion type to match with the other
    // uses inside of the binary. Pointer auth cares about the SIL function
    // type.
    if (callee->getType().castTo<SILFunctionType>()->getExtInfo().getRepresentation() ==
        SILFunctionTypeRepresentation::ObjCMethod) {
      CanSILFunctionType newFnType =
        MapperCache.getNewSILFunctionType(
          genEnv,
          callee->getType().castTo<SILFunctionType>(), *currIRMod,
          /*mustTransform*/ true);
      SILType newType = SILType::getPrimitiveObjectType(newFnType);
      callee = applyBuilder.createConvertFunction(castedApply->getLoc(),
                                                  callee, newType, false);
    }
    auto newApply = applyBuilder.createPartialApply(
        castedApply->getLoc(), callee, applySite.getSubstitutionMap(), callArgs,
        partialApplyConvention, resultIsolation, castedApply->isOnStack(),
        castedApply->isStackAllocationNested());
    castedApply->replaceAllUsesWith(newApply);
    break;
  }
  default:
    llvm_unreachable("Unexpected instr: unknown apply type");
  }
  Delete.push_back(applyInst);
}

bool LoadableByAddress::recreateApply(
    SILInstruction &I, SmallVectorImpl<SILInstruction *> &Delete) {
  if (!modApplies.count(&I))
    return false;
  recreateSingleApply(&I, Delete);
  modApplies.remove(&I);
  return true;
}

bool LoadableByAddress::recreateLoadInstr(
    SILInstruction &I, SmallVectorImpl<SILInstruction *> &Delete) {
  auto *loadInstr = dyn_cast<LoadInst>(&I);
  if (!loadInstr || !loadInstrsOfFunc.count(loadInstr))
    return false;

  SILBuilderWithScope loadBuilder(loadInstr);
  // If this is a load of a function for which we changed the return type:
  // add UncheckedBitCast before the load
  auto loadOp = loadInstr->getOperand();
  loadOp = getOperandTypeWithCastIfNecessary(
      loadInstr, loadOp, *getIRGenModule(), loadBuilder, MapperCache);
  auto *newInstr = loadBuilder.createLoad(loadInstr->getLoc(), loadOp,
                                          loadInstr->getOwnershipQualifier());
  loadInstr->replaceAllUsesWith(newInstr);
  Delete.push_back(loadInstr);
  return true;
}

bool LoadableByAddress::recreateUncheckedEnumDataInstr(
    SILInstruction &I, SmallVectorImpl<SILInstruction *> &Delete) {
  auto enumInstr = dyn_cast<UncheckedEnumDataInst>(&I);
  if (!enumInstr || !uncheckedEnumDataOfFunc.count(enumInstr))
    return false;
  SILBuilderWithScope enumBuilder(enumInstr);
  SILFunction *F = enumInstr->getFunction();
  IRGenModule *currIRMod = getIRGenModule()->IRGen.getGenModule(F);
  SILType origType = enumInstr->getType();
  GenericEnvironment *genEnv = getSubstGenericEnvironment(F);
  SILType newType = MapperCache.getNewSILType(genEnv, origType, *currIRMod);
  auto caseTy = enumInstr->getOperand()->getType().getEnumElementType(
      enumInstr->getElement(), F->getModule(), TypeExpansionContext(*F));
  SingleValueInstruction *newInstr = nullptr;
  if (newType.isAddress()) {
    newType = newType.getObjectType();
  }
  if (caseTy != newType) {
    auto *takeEnum = enumBuilder.createUncheckedEnumData(
        enumInstr->getLoc(), enumInstr->getOperand(), enumInstr->getElement(),
        caseTy);
    newInstr = enumBuilder.createUncheckedReinterpretCast(enumInstr->getLoc(),
                                                          takeEnum, newType);
  } else {
    newInstr = enumBuilder.createUncheckedEnumData(
        enumInstr->getLoc(), enumInstr->getOperand(), enumInstr->getElement(),
        newType);
  }
  enumInstr->replaceAllUsesWith(newInstr);
  Delete.push_back(enumInstr);
  return false;
}

bool LoadableByAddress::recreateUncheckedEnumDataAddrInst(
    SILInstruction &I, SmallVectorImpl<SILInstruction *> &Delete) {
  auto *enumInstr = dyn_cast<UncheckedEnumDataAddrInstBase>(&I);
  if (!enumInstr || !uncheckedEnumDataAddrOfFunc.count(enumInstr))
    return false;

  SILBuilderWithScope enumBuilder(enumInstr);

  auto recreateInst = [&](SILType newTy) -> UncheckedEnumDataAddrInstBase* {
    switch (enumInstr->getKind()) {
    case SILInstructionKind::UncheckedTakeEnumDataAddrInst:
      return enumBuilder.createUncheckedTakeEnumDataAddr(
        enumInstr->getLoc(), enumInstr->getEnum(), enumInstr->getElement(),
        newTy);
    case SILInstructionKind::UncheckedInPlaceEnumDataAddrInst:
      return enumBuilder.createUncheckedInPlaceEnumDataAddr(
        enumInstr->getLoc(), enumInstr->getEnum(), enumInstr->getElement(),
        newTy);
    case SILInstructionKind::UncheckedBorrowEnumDataAddrInst: {
      auto b = cast<UncheckedBorrowEnumDataAddrInst>(enumInstr);
      return enumBuilder.createUncheckedBorrowEnumDataAddr(
        b->getLoc(), b->getEnum(), b->getScratch(), b->getElement(),
        newTy);
    }
    default:
      llvm_unreachable("not an enum_data_addr inst");
    }
  };
  
  SILFunction *F = enumInstr->getFunction();
  IRGenModule *currIRMod = getIRGenModule()->IRGen.getGenModule(F);
  SILType origType = enumInstr->getType();
  GenericEnvironment *genEnv = getSubstGenericEnvironment(F);
  SILType newType = MapperCache.getNewSILType(genEnv, origType, *currIRMod);
  auto caseTy = enumInstr->getEnum()->getType().getEnumElementType(
      enumInstr->getElement(), F->getModule(), TypeExpansionContext(*F));
  SingleValueInstruction *newInstr = nullptr;
  if (caseTy != origType.getObjectType()) {
    auto *takeEnum = recreateInst(caseTy.getAddressType());
    newInstr = enumBuilder.createUncheckedAddrCast(
        enumInstr->getLoc(), takeEnum, newType.getAddressType());
  } else {
    newInstr = recreateInst(newType.getAddressType());
  }
  enumInstr->replaceAllUsesWith(newInstr);
  Delete.push_back(enumInstr);
  return true;
}

bool LoadableByAddress::fixStoreToBlockStorageInstr(
    SILInstruction &I, SmallVectorImpl<SILInstruction *> &Delete) {
  auto *instr = dyn_cast<StoreInst>(&I);
  if (!instr || !storeToBlockStorageInstrs.count(instr))
    return false;
  auto dest = instr->getDest();
  auto destBlock = cast<ProjectBlockStorageInst>(dest);
  SILType destType = destBlock->getType();
  auto src = instr->getSrc();
  SILType srcType = src->getType();
  if (destType.getObjectType() != srcType) {
    // Add cast to destType
    SILBuilderWithScope castBuilder(instr);
    auto *castInstr = castBuilder.createUncheckedReinterpretCast(
        instr->getLoc(), src, destType.getObjectType());
    instr->setOperand(StoreInst::Src, castInstr);
  }
  return true;
}

bool LoadableByAddress::recreateDifferentiabilityWitnessFunction(
    SILInstruction &I, SmallVectorImpl<SILInstruction *> &Delete) {
  auto *instr = dyn_cast<DifferentiabilityWitnessFunctionInst>(&I);
  if (!instr)
    return false;

  // Check if we need to recreate the instruction.
  auto *currIRMod = getIRGenModule()->IRGen.getGenModule(instr->getFunction());
  auto resultFnTy = instr->getType().castTo<SILFunctionType>();
  auto genSig = resultFnTy->getSubstGenericSignature();
  auto *genEnv = genSig.getGenericEnvironment();
  auto newResultFnTy =
      MapperCache.getNewSILFunctionType(genEnv, resultFnTy, *currIRMod);
  if (resultFnTy == newResultFnTy)
    return true;

  SILBuilderWithScope builder(instr);
  auto *newInstr = builder.createDifferentiabilityWitnessFunction(
      instr->getLoc(), instr->getWitnessKind(), instr->getWitness(),
      SILType::getPrimitiveObjectType(newResultFnTy));
  instr->replaceAllUsesWith(newInstr);
  Delete.push_back(instr);
  return true;
}

bool LoadableByAddress::recreateTupleInstr(
    SILInstruction &I, SmallVectorImpl<SILInstruction *> &Delete) {
  auto *tupleInstr = dyn_cast<TupleInst>(&I);
  if (!tupleInstr)
    return false;

  // Check if we need to recreate the tuple:
  auto *F = tupleInstr->getFunction();
  auto *currIRMod = getIRGenModule()->IRGen.getGenModule(F);
  GenericEnvironment *genEnv = getSubstGenericEnvironment(F);
  auto resultTy = tupleInstr->getType();
  auto newResultTy = MapperCache.getNewSILType(genEnv, resultTy, *currIRMod);
  if (resultTy == newResultTy)
    return true;

  // The tuple type have changed based on its members.
  // For example if one or more of them are ‘large’ loadable types
  SILBuilderWithScope tupleBuilder(tupleInstr);
  SmallVector<SILValue, 8> elems;
  for (auto elem : tupleInstr->getElements()) {
    elems.push_back(elem);
  }
  auto *newTuple = tupleBuilder.createTuple(tupleInstr->getLoc(), newResultTy,
                                            elems);
  tupleInstr->replaceAllUsesWith(newTuple);
  Delete.push_back(tupleInstr);
  return true;
}

bool LoadableByAddress::recreateConvInstr(SILInstruction &I,
                         SmallVectorImpl<SILInstruction *> &Delete) {
  auto *convInstr = dyn_cast<SingleValueInstruction>(&I);
  if (!convInstr || !conversionInstrs.count(convInstr))
    return false;
  IRGenModule *currIRMod =
      getIRGenModule()->IRGen.getGenModule(convInstr->getFunction());
  SILType currSILType = convInstr->getType();
  auto currSILFunctionType = currSILType.castTo<SILFunctionType>();
  GenericEnvironment *genEnv =
    getSubstGenericEnvironment(convInstr->getFunction());
  // Differentiable function conversion instructions can happen while the
  // function is still generic. In that case, we must calculate the new type
  // using the converted function's generic environment rather than the
  // converting function's generic environment.
  //
  // This happens in witness thunks for default implementations of derivative
  // requirements.
  if (convInstr->getKind() == SILInstructionKind::DifferentiableFunctionInst ||
      convInstr->getKind() == SILInstructionKind::DifferentiableFunctionExtractInst ||
      convInstr->getKind() == SILInstructionKind::LinearFunctionInst ||
      convInstr->getKind() == SILInstructionKind::LinearFunctionExtractInst) {
    genEnv = currSILFunctionType->getSubstGenericSignature().getGenericEnvironment();
  }
  CanSILFunctionType newFnType = MapperCache.getNewSILFunctionType(
      genEnv, currSILFunctionType, *currIRMod);
  SILType newType = SILType::getPrimitiveObjectType(newFnType);
  SILBuilderWithScope convBuilder(convInstr);
  SingleValueInstruction *newInstr = nullptr;
  switch (convInstr->getKind()) {
  case SILInstructionKind::ThinToThickFunctionInst: {
    auto instr = cast<ThinToThickFunctionInst>(convInstr);
    newInstr = convBuilder.createThinToThickFunction(
        instr->getLoc(), instr->getOperand(), newType);
    break;
  }
  case SILInstructionKind::ConvertFunctionInst: {
    auto instr = cast<ConvertFunctionInst>(convInstr);
    newInstr = convBuilder.createConvertFunction(
        instr->getLoc(), instr->getOperand(), newType,
        instr->withoutActuallyEscaping());
    break;
  }
  case SILInstructionKind::ConvertEscapeToNoEscapeInst: {
    auto instr = cast<ConvertEscapeToNoEscapeInst>(convInstr);
    newInstr = convBuilder.createConvertEscapeToNoEscape(
        instr->getLoc(), instr->getOperand(), newType,
        instr->isLifetimeGuaranteed());
    break;
  }
  case SILInstructionKind::MarkDependenceInst: {
    auto instr = cast<MarkDependenceInst>(convInstr);
    newInstr = convBuilder.createMarkDependence(
      instr->getLoc(), instr->getValue(), instr->getBase(),
      instr->dependenceKind());
    break;
  }
  case SILInstructionKind::DifferentiableFunctionInst: {
    auto instr = cast<DifferentiableFunctionInst>(convInstr);
    newInstr = convBuilder.createDifferentiableFunction(
        instr->getLoc(), instr->getParameterIndices(),
        instr->getResultIndices(), instr->getOriginalFunction(),
        instr->getOptionalDerivativeFunctionPair());
    break;
  }
  case SILInstructionKind::DifferentiableFunctionExtractInst: {
    auto instr = cast<DifferentiableFunctionExtractInst>(convInstr);
    // Rewrite `differentiable_function_extract` with explicit extractee type.
    newInstr = convBuilder.createDifferentiableFunctionExtract(
        instr->getLoc(), instr->getExtractee(), instr->getOperand(), newType);
    break;
  }
  case SILInstructionKind::LinearFunctionInst: {
    auto instr = cast<LinearFunctionInst>(convInstr);
    newInstr = convBuilder.createLinearFunction(
        instr->getLoc(), instr->getParameterIndices(),
        instr->getOriginalFunction(), instr->getOptionalTransposeFunction());
    break;
  }
  case SILInstructionKind::LinearFunctionExtractInst: {
    auto instr = cast<LinearFunctionExtractInst>(convInstr);
    newInstr = convBuilder.createLinearFunctionExtract(
        instr->getLoc(), instr->getExtractee(), instr->getOperand());
    break;
  }
  default:
    llvm_unreachable("Unexpected conversion instruction");
  }
  convInstr->replaceAllUsesWith(newInstr);
  Delete.push_back(convInstr);
  return true;
}

bool LoadableByAddress::recreateBuiltinInstr(SILInstruction &I,
                         SmallVectorImpl<SILInstruction *> &Delete) {
	auto builtinInstr = dyn_cast<BuiltinInst>(&I);
  if (!builtinInstr || !builtinInstrs.count(builtinInstr))
    return false;
  auto *currIRMod =
      getIRGenModule()->IRGen.getGenModule(builtinInstr->getFunction());
  auto *F = builtinInstr->getFunction();
  GenericEnvironment *genEnv = getSubstGenericEnvironment(F);
  auto resultTy = builtinInstr->getType();
  auto newResultTy = MapperCache.getNewSILType(genEnv, resultTy, *currIRMod);

  llvm::SmallVector<SILValue, 5> newArgs;
  for (auto oldArg : builtinInstr->getArguments()) {
    newArgs.push_back(oldArg);
  }

  SILBuilderWithScope builtinBuilder(builtinInstr);
  auto *newInstr = builtinBuilder.createBuiltin(
      builtinInstr->getLoc(), builtinInstr->getName(), newResultTy,
      builtinInstr->getSubstitutions(), newArgs);
  builtinInstr->replaceAllUsesWith(newInstr);
  Delete.push_back(builtinInstr);
  return true;
}

void LoadableByAddress::updateLoweredTypes(SILFunction *F) {
  IRGenModule *currIRMod = getIRGenModule()->IRGen.getGenModule(F);
  CanSILFunctionType funcType = F->getLoweredFunctionType();
  GenericEnvironment *genEnv = getSubstGenericEnvironment(F);
  if (!genEnv && funcType->getSubstGenericSignature()) {
    genEnv = getSubstGenericEnvironment(funcType);
  }
  auto newFuncTy =
      MapperCache.getNewSILFunctionType(genEnv, funcType, *currIRMod);
  F->rewriteLoweredTypeUnsafe(newFuncTy);
}

void IRGenModule::lowerSILFunction(SILFunction *f) {
  CanSILFunctionType funcType = f->getLoweredFunctionType();
  GenericEnvironment *genEnv = getSubstGenericEnvironment(f);
  if (!genEnv && funcType->getSubstGenericSignature()) {
    genEnv = getSubstGenericEnvironment(funcType);
  }
  LargeSILTypeMapper MapperCache;
  auto newFuncTy = MapperCache.getNewSILFunctionType(genEnv, funcType, *this);
  f->rewriteLoweredTypeUnsafe(newFuncTy);
}

bool LoadableByAddress::shouldTransformGlobal(SILGlobalVariable *global) {
  SILInstruction *init = global->getStaticInitializerValue();
  if (!init)
    return false;
  auto silTy = global->getLoweredType();
  if (!isa<SILFunctionType>(silTy.getASTType()))
    return false;

  auto *decl = global->getDecl();
  IRGenModule *currIRMod = getIRGenModule()->IRGen.getGenModule(
      decl ? decl->getDeclContext() : nullptr);
  auto silFnTy = global->getLoweredFunctionType();
  GenericEnvironment *genEnv = getSubstGenericEnvironment(silFnTy);
  if (MapperCache.shouldTransformFunctionType(genEnv, silFnTy, *currIRMod))
    return true;
  return false;
}

bool LoadableByAddress::shouldTransformInitExprOfGlobal(SILGlobalVariable *global) {
  for (const SILInstruction &initInst : *global) {
    if (auto *fri = dyn_cast<FunctionRefBaseInst>(&initInst)) {
      SILFunction *refF = fri->getInitiallyReferencedFunction();
      if (modFuncs.count(refF) != 0)
        return true;
    }
  }
  return false;
}

namespace {
  class GlobalInitCloner : public SILCloner<GlobalInitCloner> {
    LoadableByAddress *pass;
    IRGenModule *irgenModule;
  public:
    GlobalInitCloner(SILGlobalVariable *global, LoadableByAddress *pass,
                     IRGenModule *irgenModule)
      : SILCloner<GlobalInitCloner>(global), pass(pass), irgenModule(irgenModule) {
    }

    SILType remapType(SILType ty) {
      ty = ty.subst(getBuilder().getModule(), Functor, Functor);
      if (auto fnType = ty.getAs<SILFunctionType>()) {
        GenericEnvironment *genEnv = getSubstGenericEnvironment(fnType);
        return SILType::getPrimitiveObjectType(
            pass->MapperCache.getNewSILFunctionType(genEnv, fnType, *irgenModule));
      }
      return ty;
    }

    void clone(SILInstruction *inst) {
      visit(inst);
    }
  };
}

static void runPeepholesAndReg2Mem(SILPassManager *pm, SILModule *silMod,
                                   IRGenModule *irgenModule);

/// The entry point to this function transformation.
void LoadableByAddress::run() {  
  // Set the SIL state before the PassManager has a chance to run
  // verification.
  getModule()->setStage(SILStage::Lowered);

  for (auto &F : *getModule())
    runOnFunction(&F);

  if (modFuncs.empty() && modApplies.empty()) {
    runPeepholesAndReg2Mem(getPassManager(), getModule(), getIRGenModule());
    return;
  }

  // Scan the module for all references of the modified functions:
  llvm::SetVector<FunctionRefBaseInst *> funcRefs;
  llvm::SetVector<SILInstruction *> globalRefs;
  for (SILFunction &CurrF : *getModule()) {
    for (SILBasicBlock &BB : CurrF) {
      for (SILInstruction &I : BB) {
        if (auto *allocGlobal = dyn_cast<AllocGlobalInst>(&I)) {
          auto *global = allocGlobal->getReferencedGlobal();
          if (shouldTransformGlobal(global))
            globalRefs.insert(allocGlobal);
        } else if (auto *globalAddr = dyn_cast<GlobalAddrInst>(&I)) {
          auto *global = globalAddr->getReferencedGlobal();
          if (shouldTransformGlobal(global))
            globalRefs.insert(globalAddr);
        } else if (auto *globalVal = dyn_cast<GlobalValueInst>(&I)) {
          auto *global = globalVal->getReferencedGlobal();
          if (shouldTransformGlobal(global))
            globalRefs.insert(globalVal);
        } else if (auto *FRI = dyn_cast<FunctionRefBaseInst>(&I)) {
          SILFunction *RefF = FRI->getInitiallyReferencedFunction();
          if (modFuncs.count(RefF) != 0) {
            // Go over the uses and add them to lists to modify
            //
            // FIXME: Why aren't function_ref uses processed transitively?  And
            // why is it necessary to visit uses at all if they will be visited
            // later in this loop?
            for (auto *user : FRI->getUses()) {
              SILInstruction *currInstr = user->getUser();
              switch (currInstr->getKind()) {
              case SILInstructionKind::ApplyInst:
              case SILInstructionKind::TryApplyInst:
              case SILInstructionKind::BeginApplyInst:
              case SILInstructionKind::PartialApplyInst: {
                if (modApplies.count(currInstr) == 0) {
                  modApplies.insert(currInstr);
                }
                break;
              }
              case SILInstructionKind::ConvertFunctionInst:
              case SILInstructionKind::ConvertEscapeToNoEscapeInst:
              case SILInstructionKind::MarkDependenceInst:
              case SILInstructionKind::ThinToThickFunctionInst:
              case SILInstructionKind::DifferentiableFunctionInst:
              case SILInstructionKind::LinearFunctionInst:
              case SILInstructionKind::LinearFunctionExtractInst:
              case SILInstructionKind::DifferentiableFunctionExtractInst: {
                conversionInstrs.insert(
                    cast<SingleValueInstruction>(currInstr));
                break;
              }
              case SILInstructionKind::BuiltinInst: {
                auto *instr = cast<BuiltinInst>(currInstr);
                builtinInstrs.insert(instr);
                break;
              }
              case SILInstructionKind::BranchInst:
              case SILInstructionKind::StructInst:
              case SILInstructionKind::DebugValueInst:
                break;
              default:
#ifndef NDEBUG
                currInstr->dump();
                currInstr->getFunction()->dump();
#endif
                llvm_unreachable("Unhandled use of FunctionRefInst");
              }
            }
            funcRefs.insert(FRI);
          }
        } else if (auto *Cvt = dyn_cast<MarkDependenceInst>(&I)) {
          SILValue val = Cvt->getValue();
          SILType currType = val->getType();
          if (auto fType = currType.getAs<SILFunctionType>()) {
            if (modifiableFunction(fType)) {
              conversionInstrs.insert(Cvt);
            }
          }
        } else if (auto *Cvt = dyn_cast<ConvertEscapeToNoEscapeInst>(&I)) {
          SILValue val = Cvt->getOperand();
          SILType currType = val->getType();
          auto fType = currType.getAs<SILFunctionType>();
          assert(fType && "Expected SILFunctionType");
          if (modifiableFunction(fType)) {
            conversionInstrs.insert(Cvt);
          }
        } else if (auto *CFI = dyn_cast<ConvertFunctionInst>(&I)) {
          SILValue val = CFI->getOperand();
          SILType currType = val->getType();
          auto fType = currType.getAs<SILFunctionType>();
          assert(fType && "Expected SILFunctionType");
          if (modifiableFunction(fType)) {
            conversionInstrs.insert(CFI);
          }
        } else if (auto *TTI = dyn_cast<ThinToThickFunctionInst>(&I)) {

          auto canType = TTI->getCallee()->getType();
          auto fType = canType.castTo<SILFunctionType>();

          if (modifiableFunction(fType))
            conversionInstrs.insert(TTI);

        } else if (auto *LI = dyn_cast<LoadInst>(&I)) {
          loadInstrsOfFunc.insert(LI);
        } else if (auto *UED = dyn_cast<UncheckedEnumDataInst>(&I)) {
          uncheckedEnumDataOfFunc.insert(UED);
        } else if (auto *UED = dyn_cast<UncheckedEnumDataAddrInstBase>(&I)) {
          uncheckedEnumDataAddrOfFunc.insert(UED);
        } else if (auto *SI = dyn_cast<StoreInst>(&I)) {
          auto dest = SI->getDest();
          if (isa<ProjectBlockStorageInst>(dest)) {
            storeToBlockStorageInstrs.insert(SI);
          }
        } else if (auto *PAI = dyn_cast<PartialApplyInst>(&I)) {
          if (modApplies.count(PAI) == 0) {
            modApplies.insert(PAI);
          }
        } else if (isa<DifferentiableFunctionInst>(&I) ||
                   isa<LinearFunctionInst>(&I) ||
                   isa<DifferentiableFunctionExtractInst>(&I) ||
                   isa<LinearFunctionExtractInst>(&I)) {
          conversionInstrs.insert(cast<SingleValueInstruction>(&I));
        }
      }
    }
  }

  for (auto *F : modFuncs) {
    // Update the lowered type of the Function
    updateLoweredTypes(F);
  }

  // Update globals' initializer.
  SmallVector<SILGlobalVariable *, 16> deadGlobals;
  for (SILGlobalVariable &global : getModule()->getSILGlobals()) {
    if (shouldTransformInitExprOfGlobal(&global)) {
      auto *decl = global.getDecl();
      IRGenModule *currIRMod = getIRGenModule()->IRGen.getGenModule(
          decl ? decl->getDeclContext() : nullptr);

      auto silTy = global.getLoweredType();
      if (isa<SILFunctionType>(silTy.getASTType())) {
        auto silFnTy = global.getLoweredFunctionType();
        GenericEnvironment *genEnv = getSubstGenericEnvironment(silFnTy);
        if (MapperCache.shouldTransformFunctionType(genEnv, silFnTy, *currIRMod)) {
            auto newSILFnType =
              MapperCache.getNewSILFunctionType(genEnv, silFnTy, *currIRMod);
            global.unsafeSetLoweredType(SILType::getPrimitiveObjectType(newSILFnType));
        }
      }

      // Rewrite the init basic block...
      SmallVector<SILInstruction *, 8> initBlockInsts;
      for (auto it = global.begin(), end = global.end(); it != end; ++it) {
        initBlockInsts.push_back(const_cast<SILInstruction *>(&*it));
      }
      GlobalInitCloner cloner(&global, this, currIRMod);
      for (auto *oldInst : initBlockInsts) {
        cloner.clone(oldInst);
        global.unsafeRemove(oldInst, *getModule());
      }
    }
  }

  // Rewrite global variable users.
  for (auto *inst : globalRefs) {
    if (auto *allocGlobal = dyn_cast<AllocGlobalInst>(inst)) {
      // alloc_global produces no results.
      SILBuilderWithScope builder(inst);
      builder.createAllocGlobal(allocGlobal->getLoc(),
                                allocGlobal->getReferencedGlobal());
      allocGlobal->eraseFromParent();
    } else if (auto *globalAddr = dyn_cast<GlobalAddrInst>(inst)) {
      SILBuilderWithScope builder(inst);
      auto *newInst = builder.createGlobalAddr(
          globalAddr->getLoc(), globalAddr->getReferencedGlobal(),
          globalAddr->getDependencyToken());
      globalAddr->replaceAllUsesWith(newInst);
      globalAddr->eraseFromParent();
    } else if (auto *globalVal = dyn_cast<GlobalValueInst>(inst)) {
      SILBuilderWithScope builder(inst);
      auto *newInst = builder.createGlobalValue(
          globalVal->getLoc(), globalVal->getReferencedGlobal(), globalVal->isBare());
      globalVal->replaceAllUsesWith(newInst);
      globalVal->eraseFromParent();
    }
  }

  // Update all references:
  // Note: We don't need to update the witness tables and vtables
  // They just contain a pointer to the function
  // The pointer does not change
  for (auto *instr : funcRefs) {
    SILFunction *F = instr->getInitiallyReferencedFunction();
    SILBuilderWithScope refBuilder(instr);
    SingleValueInstruction *newInstr =
        refBuilder.createFunctionRef(instr->getLoc(), F, instr->getKind());
    instr->replaceAllUsesWith(newInstr);
    instr->getParent()->erase(instr);
  }

  // Recreate the instructions in topological order. Some instructions inherit
  // their result type from their operand.
  for (SILFunction &CurrF : *getModule()) {
    SmallVector<SILInstruction *, 32> Delete;
    for (SILBasicBlock &BB : CurrF) {
      for (SILInstruction &I : BB) {
        if (recreateTupleInstr(I, Delete))
          continue;
        else if (recreateConvInstr(I, Delete))
          continue;
        else if (recreateBuiltinInstr(I, Delete))
          continue;
        else if (recreateUncheckedEnumDataInstr(I, Delete))
          continue;
        else if (recreateUncheckedEnumDataAddrInst(I, Delete))
          continue;
        else if (recreateLoadInstr(I, Delete))
          continue;
        else if (recreateApply(I, Delete))
          continue;
        else if (recreateDifferentiabilityWitnessFunction(I, Delete))
          continue;
        else
          fixStoreToBlockStorageInstr(I, Delete);
      }
    }
    for (auto *Inst : Delete)
      Inst->eraseFromParent();
  }

  // Clean up the data structs:
  modFuncs.clear();
  conversionInstrs.clear();
  loadInstrsOfFunc.clear();
  uncheckedEnumDataOfFunc.clear();
  modApplies.clear();
  storeToBlockStorageInstrs.clear();

  getPassManager()->invalidateAllAnalysis();
  runPeepholesAndReg2Mem(getPassManager(), getModule(), getIRGenModule());
}

namespace {
struct Peepholes {
  SmallVector<SILInstruction *, 64> Delete;
  llvm::DenseSet<SILInstruction *> Ignore;

  SILPassManager *pm;
  SILModule *silMod;
  IRGenModule *irgenModule;

  Peepholes(SILPassManager *pm, SILModule *silMod, IRGenModule *irgenModule)
      : pm(pm), silMod(silMod), irgenModule(irgenModule) {}

  void markForDeletion(SILInstruction *i) {
    Delete.push_back(i);
    Ignore.insert(i);
  }

  bool ignore(SILInstruction *i) { return Ignore.count(i); }

  void deleteInstructions() {
    for (auto *inst : Delete) {
      inst->eraseFromParent();
    }
  }

  bool optimizeLoad(SILBasicBlock &BB, SILInstruction *I);
};
} // namespace

bool Peepholes::optimizeLoad(SILBasicBlock &BB, SILInstruction *I) {
  assert(!ignore(I));
  auto *LI = dyn_cast<LoadInst>(I);
  if (!LI)
    return false;

  auto nextIt = std::next(SILBasicBlock::iterator(LI));
  if (nextIt == BB.end())
    return false;

  // %4 = load %3 : $*LargeThing
  // retain_value %4 : $LargeThing
  // store %4 to %0 : $*LargeThing
  // ->
  // copy_addr %3 to [init] %0uto nextIt =
  // std::next(SILBasicBlock::iterator(LI));
  if (auto *retain = dyn_cast<RetainValueInst>(&*nextIt)) {
    if (!LI->hasTwoUses())
      return false;
    if (retain->getOperand() != LI)
      return false;
    if (ignore(retain))
      return false;

    auto next2It = std::next(nextIt);
    if (next2It == BB.end())
      return false;
    auto *store = dyn_cast<StoreInst>(&*next2It);
    if (!store || store->getSrc() != LI)
      return false;
    if (ignore(store))
      return false;

    SILBuilderWithScope builder(LI);
    builder.createCopyAddr(store->getLoc(), LI->getOperand(), store->getDest(),
                           IsNotTake, IsInitialization);
    markForDeletion(store);
    markForDeletion(retain);
    markForDeletion(LI);
    return true;
  }

  //  %5 = load %4 : $*LargeThing
  //  copy_addr [take] %0 to [initialization] %4 : $*LargeThing
  //  release_value %5 : $LargeThing
  //  ->
  //  copy_addr [take] %0 to %4
  if (auto *copyAddr = dyn_cast<CopyAddrInst>(&*nextIt)) {
    if (copyAddr->getDest() != LI->getOperand() || !copyAddr->isTakeOfSrc() ||
        !copyAddr->isInitializationOfDest() || ignore(copyAddr))
      return false;
    if (!LI->hasOneUse())
      return false;

    auto next2It = std::next(nextIt);
    if (next2It == BB.end())
      return false;

    auto *release = dyn_cast<ReleaseValueInst>(&*next2It);
    if (!release || release->getOperand() != LI || ignore(release))
      return false;

    SILBuilderWithScope builder(LI);
    builder.createCopyAddr(copyAddr->getLoc(), copyAddr->getSrc(),
                           copyAddr->getDest(), IsTake, IsNotInitialization);
    markForDeletion(release);
    markForDeletion(copyAddr);
    markForDeletion(LI);
    return true;
  }

  //  %5 = load %4 : $*LargeThing
  //  release_value %5 : $LargeThing
  //  copy_addr [take] %0 to [initialization] %4 : $*LargeThing
  //  ->
  //  copy_addr [take] %0 to %4
  if (auto *release = dyn_cast<ReleaseValueInst>(&*nextIt)) {
    if (release->getOperand() != LI || ignore(release))
      return false;

    if (!LI->hasOneUse())
      return false;

    auto next2It = std::next(nextIt);
    if (next2It == BB.end())
      return false;

    auto *copyAddr = dyn_cast<CopyAddrInst>(&*next2It);
    if (!copyAddr || copyAddr->getDest() != LI->getOperand() ||
        !copyAddr->isTakeOfSrc() || !copyAddr->isInitializationOfDest() ||
        ignore(copyAddr))
      return false;

    SILBuilderWithScope builder(LI);
    builder.createCopyAddr(copyAddr->getLoc(), copyAddr->getSrc(),
                           copyAddr->getDest(), IsTake, IsNotInitialization);
    markForDeletion(release);
    markForDeletion(copyAddr);
    markForDeletion(LI);
    return true;
  }

  return false;
}

namespace {
struct Properties {
private:
  bool sawConstructor = false;
  bool sawProjection = false;
  bool sawFunctionUseOrReturn = false;
  bool sawLoad = false;
  bool sawStore = false;

  uint16_t use = 0;
  uint16_t numRegisters = 0;

public:
  static const uint16_t MaxNumUses = 65535;
  static const uint16_t MaxNumRegisters = 65535;

  void addConstructor() {
    sawConstructor = true;
  }
  bool hasConstructorUse() const {
    return sawConstructor;
  }

  void addProjection() {
    sawProjection = true;
  }
  bool hasProjection() const {
    return sawProjection;
  }

  void addFunctionUseOrReturn() {
    sawFunctionUseOrReturn = true;
  }
  bool hasFunctionUseOrReturn() const {
    return sawFunctionUseOrReturn;
  }

  void addLoad() {
    sawLoad = true;
  }
  bool hasLoad() const {
    return sawLoad;
  }

  void addStore() {
    sawStore = true;
  }
  bool hasStore() const {
    return sawStore;
  }

  void addUse() {
    if (use == MaxNumUses)
      return;
    ++use;
  }
  uint16_t numUses() const {
    return use;
  }
  void setAsVeryLargeType() {
    setNumRegisters(MaxNumRegisters);
  }
  void setNumRegisters(unsigned regs) {
    if (regs > MaxNumRegisters) {
      numRegisters = MaxNumRegisters;
      return;
    }
    numRegisters = regs;
  }
  uint16_t getNumRegisters() const {
    return numRegisters;
  }
};
}

namespace {
class LargeLoadableHeuristic {
  GenericEnvironment *genEnv;
  IRGenModule *irgenModule;
  SILFunction &fun;

  llvm::DenseMap<SILType, Properties> largeTypeProperties;

  /// Cutoff used to determine when to deem a type as large solely on the number
  /// of registers.
  static const unsigned NumRegistersVeryLargeType = 16;
  /// Cutoff when we start considering other properties to determine when we
  /// deem a type as large.
  static const unsigned NumRegistersLargeType = 8;
  /// Cutoff when we start to deem a type as large based on the number of uses
  /// (requires the number or registers to be at least 8).
  static const unsigned NumUsesThreshold = 8;

  bool UseAggressiveHeuristic;

public:
   LargeLoadableHeuristic(IRGenModule *irgenModule, GenericEnvironment *genEnv,
                          SILFunction &f,
                          bool UseAggressiveHeuristic)
      : genEnv(genEnv), irgenModule(irgenModule), fun(f),
      UseAggressiveHeuristic(UseAggressiveHeuristic) {}

   void visit(SILInstruction *i);
   void visit(SILArgument *arg);

   bool isLargeLoadableType(SILType ty);
   bool isPotentiallyCArray(SILType ty);

   void propagate(PostOrderFunctionInfo &po);

private:
  bool isLargeLoadableTypeOld(SILType ty);

  unsigned numRegisters(SILType ty) {
    if (ty.isAddress() || ty.isClassOrClassMetatype()) {
      return 0;
    }

    auto canType = ty.getASTType();
    if (canType->hasTypeParameter()) {
      assert(genEnv && "Expected a GenericEnv");
      canType = genEnv->mapTypeIntoEnvironment(canType)->getCanonicalType();
    }

    if (canType.getAnyGeneric() || isa<TupleType>(canType) || ty.is<BuiltinFixedArrayType>()) {
      assert(ty.isObject() &&
             "Expected only two categories: address and object");
      assert(!canType->hasTypeParameter());

      auto entry = largeTypeProperties[ty];
      auto cached = entry.getNumRegisters();
      if (cached)
        return cached;

      // Avoid computing an explosion schema for very large types as this can be
      // very compile time intensive.
      if (fun.getTypeProperties(ty).isVeryLargeType()) {
        entry.setAsVeryLargeType();
        largeTypeProperties[ty] = entry;
        return entry.getNumRegisters();
      }

      const TypeInfo &TI = irgenModule->getTypeInfoForLowered(canType);
      auto &nativeSchemaOrigParam = TI.nativeParameterValueSchema(*irgenModule);
      // Passed compactly in registers but kept in many explosions.
      // An example of this is a C struct with a char buffer e.g
      // `struct {char buf[28]; }`.
      auto explosionSchema = TI.getSchema();
      auto res = std::max(nativeSchemaOrigParam.size(), explosionSchema.size());
      entry.setNumRegisters(res);
      largeTypeProperties[ty] = entry;
      return entry.getNumRegisters();
    }

    return 0;
  }
};
}

void LargeLoadableHeuristic::propagate(PostOrderFunctionInfo &po) {
  if (!UseAggressiveHeuristic)
    return;

  for (auto *BB : po.getPostOrder()) {
    for (auto &I : llvm::reverse(*BB)) {
      switch (I.getKind()) {
      case SILInstructionKind::UncheckedBitwiseCastInst:
      case SILInstructionKind::TupleExtractInst:
      case SILInstructionKind::StructExtractInst: {
        auto &proj = cast<SingleValueInstruction>(I);
        if (isLargeLoadableType(proj.getType())) {
          auto opdTy = proj.getOperand(0)->getType();
          auto entry = largeTypeProperties[opdTy];
          entry.setAsVeryLargeType();
          largeTypeProperties[opdTy] = entry;
        }
      }
      break;

      default:
        continue;
      }
    }
  }
}

void LargeLoadableHeuristic::visit(SILArgument *arg) {
  if (arg->getType().isAddress())
    return;

  auto objType = arg->getType().getObjectType();
  if (numRegisters(objType) < NumRegistersLargeType)
    return;

  auto entry = largeTypeProperties[objType];
  for (auto *use : arg->getUses()) {
    auto *usr = use->getUser();
    switch (usr->getKind()) {
    case SILInstructionKind::TupleExtractInst:
    case SILInstructionKind::StructExtractInst: {
      auto projectionTy = cast<SingleValueInstruction>(usr)->getType();
      if (numRegisters(projectionTy) >= NumRegistersLargeType) {
        entry.addProjection();

        largeTypeProperties[objType] = entry;
      }
      break;
    }
    default:
      continue;
    }
  }
}
void LargeLoadableHeuristic::visit(SILInstruction *i) {
  if (!UseAggressiveHeuristic)
    return;

  // Heuristic to make sure that UncheckedBitCast on C unions don't cause
  // trouble (the type of a union has a single llvm register representing the
  // bitwidth).
  if (auto *bitcast = dyn_cast<UncheckedTrivialBitCastInst>(i)) {
    auto opdTy = bitcast->getOperand()->getType();
    auto numRegs = numRegisters(opdTy);
    if (numRegs < NumRegistersLargeType) {
      auto resTy = bitcast->getType();
      if (numRegisters(resTy) > NumRegistersLargeType) {
        // Force the source type to be indirect.
        auto entry = largeTypeProperties[opdTy];
        entry.setAsVeryLargeType();
        largeTypeProperties[opdTy] = entry;
        return;
      }
    }
  }

  for (const auto &opd : i->getAllOperands()) {
    auto opdTy = opd.get()->getType();
    auto objType = opdTy.getObjectType();
    bool isAddress = opdTy.isAddress();

    auto registerCount = numRegisters(objType);
    if (registerCount < NumRegistersLargeType)
      continue;

    auto entry = largeTypeProperties[objType];

    switch (i->getKind()) {
    case SILInstructionKind::TupleExtractInst:
    case SILInstructionKind::StructExtractInst: {
      auto projectionTy = cast<SingleValueInstruction>(i)->getType();
      if (numRegisters(projectionTy) >= NumRegistersLargeType)
        entry.addProjection();
      break;
    }
    case SILInstructionKind::EnumInst:
    case SILInstructionKind::TupleInst:
    case SILInstructionKind::StructInst: {
      entry.addConstructor();
      auto userType = cast<SingleValueInstruction>(i)->getType();
      auto &userEntry = largeTypeProperties[userType];
      userEntry.addConstructor();
      break;
    }
    case SILInstructionKind::SwitchEnumInst: {
      for (auto &succ : i->getParent()->getSuccessors()) {
        auto *caseBB = succ.getBB();
        if (caseBB->getArguments().size() == 0)
          continue;
        assert(caseBB->getArguments().size() == 1);
        auto caseTy = caseBB->getArgument(0)->getType();
        if (numRegisters(caseTy) >= NumRegistersLargeType)
          entry.addProjection();
      }
      break;
    }
    case SILInstructionKind::SelectEnumInst:
      break;
    case SILInstructionKind::LoadInst:
      entry.addLoad();
      break;
    case SILInstructionKind::StoreInst:
      entry.addStore();
      break;
    case SILInstructionKind::ReturnInst:
    case SILInstructionKind::ApplyInst:
    case SILInstructionKind::PartialApplyInst:
    case SILInstructionKind::TryApplyInst:
    case SILInstructionKind::BeginApplyInst:
      if (!isAddress)
        entry.addFunctionUseOrReturn();
      break;
    default:
      if (!isAddress)
        entry.addUse();
      break;
    }

    largeTypeProperties[objType] = entry;
  }
}

bool LargeLoadableHeuristic::isPotentiallyCArray(SILType ty) {
  if (ty.isAddress() || ty.isClassOrClassMetatype()) {
    return false;
  }

  auto canType = ty.getASTType();
  if (canType->hasTypeParameter()) {
    assert(genEnv && "Expected a GenericEnv");
    canType = genEnv->mapTypeIntoEnvironment(canType)->getCanonicalType();
  }

  if (canType.getAnyGeneric() || isa<TupleType>(canType)) {
    assert(ty.isObject() &&
           "Expected only two categories: address and object");
    assert(!canType->hasTypeParameter());
    const TypeInfo &TI = irgenModule->getTypeInfoForLowered(canType);
    auto explosionSchema = TI.getSchema();
    if (explosionSchema.size() > 15)
      return true;
  }
  return false;
}

// The old heuristic to determine whether we deem a type as large.
bool LargeLoadableHeuristic::isLargeLoadableTypeOld(SILType ty) {
  if (ty.isAddress() || ty.isClassOrClassMetatype()) {
    return false;
  }

  auto canType = ty.getASTType();
  if (canType->hasTypeParameter()) {
    assert(genEnv && "Expected a GenericEnv");
    canType = genEnv->mapTypeIntoEnvironment(canType)->getCanonicalType();
  }

  if (canType.getAnyGeneric() || isa<TupleType>(canType)) {
    assert(ty.isObject() &&
           "Expected only two categories: address and object");
    assert(!canType->hasTypeParameter());
    const TypeInfo &TI = irgenModule->getTypeInfoForLowered(canType);
    auto &nativeSchemaOrigParam = TI.nativeParameterValueSchema(*irgenModule);
    if (nativeSchemaOrigParam.size() > 15)
      return true;
    auto explosionSchema = TI.getSchema();
    if (explosionSchema.size() > 15)
      return true;
  }
  return false;
}

bool LargeLoadableHeuristic::isLargeLoadableType(SILType ty) {
  if (!UseAggressiveHeuristic)
    return isLargeLoadableTypeOld(ty);

  auto regs = numRegisters(ty);

  if (regs < NumRegistersLargeType)
    return false;

  auto it = largeTypeProperties.find(ty);
  if (it == largeTypeProperties.end()) {
    return regs >= NumRegistersVeryLargeType;
  }
  const auto entry = it->second;

  if (regs >= NumRegistersVeryLargeType)
    return true;

  if (entry.numUses() > NumUsesThreshold)
   return true;

  if (entry.hasStore())
    return true;

  if (entry.hasConstructorUse())
    return true;

  if (entry.hasProjection())
    return true;

  if (entry.hasLoad()) {
    return entry.hasConstructorUse() || entry.hasFunctionUseOrReturn();
  }

  return false;
}

namespace {
class AddressAssignment {
  // Map from a loaded SIL SSA value to and address value.
  llvm::DenseMap<SILValue, SILValue> valueToAddressMap;
  SmallVector<AllocStackInst *, 16> allocStacks;
  SmallVector<SILInstruction *, 16> toDelete;
  SmallVector<std::pair<SILBasicBlock *, unsigned>, 16> toDeleteBlockArg;

  LargeLoadableHeuristic &heuristic;
  IRGenModule *irgenModule;
  SILFunction &currFn;

public:
  AddressAssignment(LargeLoadableHeuristic &heuristic, IRGenModule *irgenModule,
                    SILFunction &currFn)
      : heuristic(heuristic), irgenModule(irgenModule), currFn(currFn) {}

  void assign(SILInstruction *inst);

  AllocStackInst *createAllocStack(SILType ty);

  SILLocation getAutoLoc() {
    return RegularLocation::getAutoGeneratedLocation();
  }

  SILBuilder getBuilder(SILBasicBlock::iterator it) {
    SILInstruction *inst = &(*it);
    SILBuilder builder(inst->getParent(), it);
    builder.setCurrentDebugScope(inst->getDebugScope());
    return builder;
  }

  SILBuilder getTermBuilder(SILInstruction *term) {
    SILBuilder builder(term->getParent(), term->getParent()->end());
    builder.setCurrentDebugScope(term->getDebugScope());
    return builder;
  }

  const BuiltinInfo &getBuiltinInfo(Identifier id) {
    return irgenModule->getSILModule().getBuiltinInfo(id);
  }

  void markForDeletion(SILInstruction *inst) { toDelete.push_back(inst); }

  void markBlockArgumentForDeletion(SILBasicBlock *b, unsigned argIdx = 0) {
    toDeleteBlockArg.push_back(std::make_pair(b, argIdx));
  }

  bool isPotentiallyCArray(SILType ty) {
    return heuristic.isPotentiallyCArray(ty);
  }

  bool isLargeLoadableType(SILType ty) {
    return heuristic.isLargeLoadableType(ty);
  }

  bool contains(SILValue v) {
    return valueToAddressMap.find(v) != valueToAddressMap.end();
  }

  SILValue getAddressForValue(SILValue v) {
    auto it = valueToAddressMap.find(v);

    // This can happen if we deem a container type small but a contained type
    // big or we have an undef operand.
    if (it == valueToAddressMap.end()) {
      if (auto *sv = dyn_cast<SingleValueInstruction>(v)) {
        auto addr = createAllocStack(v->getType());
        auto builder = getBuilder(++sv->getIterator());
        builder.createStore(sv->getLoc(), v, addr,
                            StoreOwnershipQualifier::Unqualified);
        mapValueToAddress(v, addr);
        return addr;
      }
      if (isa<SILUndef>(v)) {
        auto undefAddr = createAllocStack(v->getType());
        mapValueToAddress(v, undefAddr);
        return undefAddr;
      }

    }
    assert(it != valueToAddressMap.end());

    return it->second;
  }

  void mapValueToAddress(SILValue val, SILValue addr) {
    assert(!valueToAddressMap.count(val));
    valueToAddressMap[val] = addr;
  }

  void finish(DominanceInfo *dominance, DeadEndBlocks *deadEnds);
};
} // namespace

void AddressAssignment::finish(DominanceInfo *dominance,
                               DeadEndBlocks *deadEnds) {

  // Narrow the lifetime of alloc_stack instructions.
  for (auto *stackLoc : allocStacks) {
    auto insertPt = dominance->getLeastCommonAncestorOfUses(stackLoc)->begin();
    stackLoc->moveBefore(&*insertPt);

    SmallVector<SILBasicBlock *, 8> boundary;
    computeDominatedBoundaryBlocks(stackLoc->getParent(), dominance, boundary);
    for (auto *block : boundary) {
      if (deadEnds->isDeadEnd(block))
        continue;
      auto builder = getBuilder(block->back().getIterator());
      builder.createDeallocStack(getAutoLoc(), stackLoc);
    }
  }
  for (auto *inst : llvm::reverse(toDelete)) {
#ifndef NDEBUG
    for (auto res : inst->getResults()) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunreachable-code-loop-increment"
      for (auto *use : res->getUses()) {
        inst->dump();
        use->getUser()->dump();
        inst->getFunction()->dump();
        break;
      }
#pragma clang diagnostic pop
    }
#endif
    inst->eraseFromParent();
  }

  for (auto bbIdx : reverse(toDeleteBlockArg)) {
    bbIdx.first->eraseArgument(bbIdx.second);
  }
  StackNesting::fixNesting(&currFn);
}

static bool isSoleUserOf(LoadInst *load, SILInstruction *next) {
  if (!load->hasOneUse())
    return false;
  if(load->getSingleUse()->getUser() != next)
    return false;
  return true;
}

/// Returns true if the address which is assigned to \p inst's result is a
/// projection of (or an alias for) the address which is assigned to \p inst's
/// operand - as opposed to a new stack location the value is copied into.
/// Must be kept in sync with the AssignAddressToDef visitors below.
static bool resultAliasesOperandAddress(AddressAssignment &assignment,
                                        SILInstruction *inst) {
  switch (inst->getKind()) {
  case SILInstructionKind::TupleExtractInst:
  case SILInstructionKind::StructExtractInst:
  case SILInstructionKind::UncheckedTrivialBitCastInst:
  case SILInstructionKind::UncheckedBitwiseCastInst:
  case SILInstructionKind::MarkDependenceInst:
  case SILInstructionKind::EndCOWMutationInst:
    return assignment.isLargeLoadableType(
        cast<SingleValueInstruction>(inst)->getType());
  default:
    return false;
  }
}

/// Returns true if \p inst forwards the address of its operand - possibly via a
/// chain of such instructions - to a result which is used somewhere else than
/// in the immediately following instruction.
///
/// In this case the operand's address needs to stay valid until those uses,
/// which is not guaranteed. For example, the address can be a stack location
/// which is deallocated right after \p inst.
static bool forwardsAddressBeyondNextInstruction(AddressAssignment &assignment,
                                                 SILInstruction *inst) {
  while (resultAliasesOperandAddress(assignment, inst)) {
    auto *result = cast<SingleValueInstruction>(inst);
    // Terminators are never address forwarding, so there is a next instruction.
    auto *next = &*std::next(inst->getIterator());
    if (!result->hasOneUse() || result->getSingleUse()->getUser() != next)
      return true;
    inst = next;
  }
  return false;
}

namespace {
class AssignAddressToDef : SILInstructionVisitor<AssignAddressToDef> {
  friend SILVisitorBase<AssignAddressToDef>;
  friend SILInstructionVisitor<AssignAddressToDef>;

  AddressAssignment &assignment;
  SILValue origValue;

public:
  AssignAddressToDef(AddressAssignment &assignment, SILValue orig)
      : assignment(assignment), origValue(orig) {}

  void rewriteValue();

protected:

  void singleValueInstructionFallback(SingleValueInstruction *inst) {
    // Map all the large arguments back to values.
    for (auto &opd : inst->getAllOperands()) {
      if (assignment.isLargeLoadableType(opd.get()->getType())) {
        auto builder = assignment.getBuilder(inst->getIterator());
        auto addr = assignment.getAddressForValue(opd.get());
        auto loaded = builder.createLoad(inst->getLoc(), addr,
                                         LoadOwnershipQualifier::Unqualified);
        opd.set(loaded);
      }
    }

    // Store the result back to a stack location.
    if (assignment.isLargeLoadableType(inst->getType())) {
      auto builder = assignment.getBuilder(++inst->getIterator());
      auto addr = assignment.createAllocStack(inst->getType());
      assignment.mapValueToAddress(origValue, addr);
      builder.createStore(inst->getLoc(), origValue, addr,
                        StoreOwnershipQualifier::Unqualified);
    }
  }

  void visitSILInstruction(SILInstruction *inst) {
#ifdef NDEBUG
    if (auto *sv = dyn_cast<SingleValueInstruction>(inst)) {
      singleValueInstructionFallback(sv);
      return;
    }
#endif

#ifndef NDEBUG
    inst->dump();
    inst->getFunction()->dump();
#endif
    llvm::report_fatal_error("Unimplemented definition");
  }

  void visitKeyPathInst(KeyPathInst *kp) {
    singleValueInstructionFallback(kp);
  }

  void visitEndCOWMutationInst(EndCOWMutationInst *endCOW) {
    // This instruction is purely for semantic tracking in SIL.
    // Simply forward the value and delete the instruction.
    auto valAddr = assignment.getAddressForValue(endCOW->getOperand());
    assignment.mapValueToAddress(endCOW, valAddr);
    assignment.markForDeletion(endCOW);
  }

  void visitMarkDependenceInst(MarkDependenceInst *mark) {
    // This instruction is purely for semantic tracking in SIL.
    // Simply forward the value and delete the instruction.
    auto valAddr = assignment.getAddressForValue(mark->getValue());
    assignment.mapValueToAddress(mark, valAddr);
    assignment.markForDeletion(mark);
  }

  void visitBeginApplyInst(BeginApplyInst *apply) {
    auto builder = assignment.getBuilder(++apply->getIterator());
    auto addr = assignment.createAllocStack(origValue->getType());
    assignment.mapValueToAddress(origValue, addr);
    for (auto &opd : apply->getAllOperands()) {
      if (assignment.contains(opd.get())) {
        auto builder = assignment.getBuilder(apply->getIterator());
        auto loaded = builder.createLoad(
            apply->getLoc(), assignment.getAddressForValue(opd.get()),
            LoadOwnershipQualifier::Unqualified);
        opd.set(loaded);
      }
    }
    builder.createStore(apply->getLoc(), origValue, addr,
                        StoreOwnershipQualifier::Unqualified);
  }

  void visitApplyInst(ApplyInst *apply) {
    // The loadable by address transformation ignores large tuple return types.
    auto builder = assignment.getBuilder(++apply->getIterator());
    auto addr = assignment.createAllocStack(apply->getType());
    assignment.mapValueToAddress(origValue, addr);
    for (auto &opd : apply->getAllOperands()) {
      if (assignment.contains(opd.get())) {
        auto builder = assignment.getBuilder(apply->getIterator());
        auto loaded = builder.createLoad(
            apply->getLoc(), assignment.getAddressForValue(opd.get()),
            LoadOwnershipQualifier::Unqualified);
        opd.set(loaded);
      }
    }
    builder.createStore(apply->getLoc(), origValue, addr,
                        StoreOwnershipQualifier::Unqualified);
  }

  void visitLoadInst(LoadInst *load) {
    auto *defInst = load->getOperand()->getDefiningInstruction();
    auto *nextInst = &*std::next(load->getIterator());
    // Forward the address of the load if its sole user immediately follows the
    // load instructions and if it was not the result of a coroutine.
    // Don't do this if the user forwards the address to a result which is used
    // beyond the following instruction, because then the address would be
    // needed at those uses. For example after the load's source location is
    // deallocated.
    if (isSoleUserOf(load, nextInst) &&
        !forwardsAddressBeyondNextInstruction(assignment, nextInst) &&
        (!defInst || !isa<BeginApplyInst>(defInst))) {
      assignment.markForDeletion(load);
      assignment.mapValueToAddress(origValue, load->getOperand());
      return;
    }

    auto builder = assignment.getBuilder(load->getIterator());
    auto addr = assignment.createAllocStack(load->getType());

    assignment.mapValueToAddress(origValue, addr);

    builder.createCopyAddr(load->getLoc(), load->getOperand(), addr, IsTake,
                           IsInitialization);
    swift::salvageLoadDebugInfo(load);
    assignment.markForDeletion(load);
  }

  void visitEnumInst(EnumInst *e) {
    auto builder = assignment.getBuilder(e->getIterator());
    auto enumAddr = assignment.createAllocStack(e->getType());
    assignment.mapValueToAddress(origValue, enumAddr);

    if (e->hasOperand()) {
      auto opd = e->getOperand();

      auto initAddr = builder.createInitEnumDataAddr(
          assignment.getAutoLoc(), enumAddr, e->getElement(),
          opd->getType().getAddressType());
      if (assignment.contains(opd)) {
        SILValue opdAddr = assignment.getAddressForValue(opd);
        builder.createCopyAddr(e->getLoc(), opdAddr, initAddr, IsTake,
                               IsInitialization);
      } else {
        builder.createStore(e->getLoc(), opd, initAddr,
                            StoreOwnershipQualifier::Unqualified);
      }
    }

    builder.createInjectEnumAddr(e->getLoc(), enumAddr, e->getElement());

    assignment.markForDeletion(e);
  }

  void visitStructInst(StructInst *s) {
    auto builder = assignment.getBuilder(s->getIterator());
    auto structAddr = assignment.createAllocStack(s->getType());
    assignment.mapValueToAddress(origValue, structAddr);

    auto fieldIt = s->getStructDecl()->getStoredProperties().begin();
    for (auto &opd : s->getAllOperands()) {
      auto projAddr = builder.createStructElementAddr(
          s->getLoc(), structAddr, *fieldIt,
          opd.get()->getType().getAddressType());
      fieldIt++;

      if (assignment.contains(opd.get())) {
        auto opdAddr = assignment.getAddressForValue(opd.get());
        builder.createCopyAddr(s->getLoc(), opdAddr, projAddr, IsTake,
                               IsInitialization);
      } else {
        builder.createStore(s->getLoc(), opd.get(), projAddr,
                            StoreOwnershipQualifier::Unqualified);
      }
    }

    assignment.markForDeletion(s);
  }

  void visitTupleInst(TupleInst *t) {
    auto builder = assignment.getBuilder(t->getIterator());
    auto tupleAddr = assignment.createAllocStack(t->getType());
    assignment.mapValueToAddress(origValue, tupleAddr);
    for (auto &opd : t->getAllOperands()) {
      auto projAddr = builder.createTupleElementAddr(
          t->getLoc(), tupleAddr, opd.getOperandNumber(),
          opd.get()->getType().getAddressType());
      if (assignment.contains(opd.get())) {
        auto opdAddr = assignment.getAddressForValue(opd.get());
        builder.createCopyAddr(t->getLoc(), opdAddr, projAddr, IsTake,
                               IsInitialization);
      } else {
        builder.createStore(t->getLoc(), opd.get(), projAddr,
                            StoreOwnershipQualifier::Unqualified);
      }
    }
    assignment.markForDeletion(t);
  }

  void visitTupleExtractInst(TupleExtractInst *extract) {
    auto builder = assignment.getBuilder(extract->getIterator());
    auto opdAddr = assignment.getAddressForValue(extract->getOperand());
    auto projAddr = builder.createTupleElementAddr(
        extract->getLoc(), opdAddr, extract->getFieldIndex(),
        extract->getType().getAddressType());
    assignment.mapValueToAddress(origValue, projAddr);
    assignment.markForDeletion(extract);
  }

  void visitStructExtractInst(StructExtractInst *extract) {
    auto builder = assignment.getBuilder(extract->getIterator());

    // Handle undef operands.
    if (isa<SILUndef>(extract->getOperand())) {
      auto extractAddr = assignment.createAllocStack(extract->getType());
      assignment.mapValueToAddress(origValue, extractAddr);
      assignment.markForDeletion(extract);
      return;
    }

    auto opdAddr = assignment.getAddressForValue(extract->getOperand());
    auto projAddr = builder.createStructElementAddr(
        extract->getLoc(), opdAddr, extract->getField(),
        extract->getType().getAddressType());
    assignment.mapValueToAddress(origValue, projAddr);
    assignment.markForDeletion(extract);
  }

  void visitUncheckedEnumDataInst(UncheckedEnumDataInst *enumData) {
    auto builder = assignment.getBuilder(enumData->getIterator());
    auto opd = enumData->getOperand();
    SILValue opdAddr;
    if (assignment.contains(opd)) {
      opdAddr = assignment.getAddressForValue(enumData->getOperand());
      // builder.createUncheckedTakeEnumDataAddr is destructive.
      // So we need to copy to keep the original address location valid.
      auto addr = assignment.createAllocStack(opd->getType());
      builder.createCopyAddr(enumData->getLoc(), opdAddr, addr, IsTake,
                             IsInitialization);
      opdAddr = addr;
      // TODO: we could omit the copy if this is the only user of the operand.
    } else {
      opdAddr = assignment.createAllocStack(opd->getType());
      builder.createStore(enumData->getLoc(), opd, opdAddr,
                          StoreOwnershipQualifier::Unqualified);
    }
    auto extractAddr = builder.createUncheckedEnumDataAddrForTake(
        enumData->getLoc(), opdAddr, enumData->getElement(),
        enumData->getType().getAddressType());
    assignment.mapValueToAddress(origValue, extractAddr);
    assignment.markForDeletion(enumData);
  }

  void visitBuiltinInst(BuiltinInst *bi) {
    if (assignment.getBuiltinInfo(bi->getName()).ID ==
        BuiltinValueKind::ZeroInitializer) {
      auto build = assignment.getBuilder(++bi->getIterator());
      auto newAddr = assignment.createAllocStack(bi->getType());
      build.createZeroInitAddr(bi->getLoc(), newAddr);
      assignment.mapValueToAddress(origValue, newAddr);
      assignment.markForDeletion(bi);
    } else {
      singleValueInstructionFallback(bi);
    }
  }

  void visitUncheckedTrivialBitCastInst(UncheckedTrivialBitCastInst *bc) {
    auto builder = assignment.getBuilder(bc->getIterator());

    if (assignment.isLargeLoadableType(bc->getType()) &&
        !assignment.isLargeLoadableType(bc->getOperand()->getType())) {
      // Curious case of an imported C union.
      // The union is imported as a struct that has no fields.
      // When we access union members we instead bitcast to the union member
      // type.

      // We expect the "in" type to be larger or equal in size to the "out"
      // type. See IRGenSILFunction::visitUncheckedTrivialBitCastInst.
      // We therefore must use the bigger type, i.e the operand type, to create
      // a stack allocation.
      auto opdAddr = assignment.createAllocStack(bc->getOperand()->getType());
      // Try load -> store forwarding.
      auto peephole = dyn_cast<LoadInst>(bc->getOperand());
      if (peephole && peephole->getParent() == bc->getParent() &&
          (++peephole->getIterator()) == bc->getIterator()) {
        builder.createCopyAddr(bc->getLoc(), peephole->getOperand(), opdAddr,
                               IsTake, IsInitialization);
      } else {
        builder.createStore(bc->getLoc(), bc->getOperand(), opdAddr,
                            StoreOwnershipQualifier::Unqualified);
      }
      auto addr = builder.createUncheckedAddrCast(
          bc->getLoc(), opdAddr, bc->getType().getAddressType());

      assignment.mapValueToAddress(origValue, addr);
      assignment.markForDeletion(bc);
      return;
    }
    auto opdAddr = assignment.getAddressForValue(bc->getOperand());
    auto newAddr = builder.createUncheckedAddrCast(
        bc->getLoc(), opdAddr, bc->getType().getAddressType());
    assignment.mapValueToAddress(origValue, newAddr);
    assignment.markForDeletion(bc);
  }

  void visitUncheckedBitwiseCastInst(UncheckedBitwiseCastInst *bc) {
    auto builder = assignment.getBuilder(bc->getIterator());
    auto opdAddr = assignment.getAddressForValue(bc->getOperand());
    auto newAddr = builder.createUncheckedAddrCast(
        bc->getLoc(), opdAddr, bc->getType().getAddressType());
    assignment.mapValueToAddress(origValue, newAddr);
    assignment.markForDeletion(bc);
  }
};
} // namespace

void AssignAddressToDef::rewriteValue() {
  auto *inst = origValue.getDefiningInstruction();
  visit(inst);
}

namespace {
class RewriteUser : SILInstructionVisitor<RewriteUser> {
  friend SILVisitorBase<RewriteUser>;
  friend SILInstructionVisitor<RewriteUser>;

  AddressAssignment &assignment;
  SILInstruction *user;

public:
  RewriteUser(AddressAssignment &assignment, SILInstruction *user)
      : assignment(assignment), user(user) {}

  void rewrite() { visit(user); }

protected:

  void userInstructionFallback(SILInstruction *inst) {
    // Map all the large arguments back to values.
    for (auto &opd : inst->getAllOperands()) {
      if (assignment.isLargeLoadableType(opd.get()->getType())) {
        auto builder = assignment.getBuilder(inst->getIterator());
        auto addr = assignment.getAddressForValue(opd.get());
        auto loaded = builder.createLoad(inst->getLoc(), addr,
                                         LoadOwnershipQualifier::Unqualified);
        opd.set(loaded);
      }
    }
  }

  void visitSILInstruction(SILInstruction *inst) {
#ifdef NDEBUG
    userInstructionFallback(inst);
    return;
#endif

#ifndef NDEBUG
    inst->dump();
    inst->getFunction()->dump();
    llvm::report_fatal_error("Unimplemented user");
#endif
  }

  void visitKeyPathInst(KeyPathInst *kp) {
    userInstructionFallback(kp);
  }

  void visitYieldInst(YieldInst *yield) { userInstructionFallback(yield); }

  void visitThrowInst(ThrowInst *t) { userInstructionFallback(t); }

  void visitCheckedCastBranchInst(CheckedCastBranchInst *c) {
    userInstructionFallback(c);
  }

  void visitFixLifetimeInst(FixLifetimeInst *f) {
    auto addr = assignment.getAddressForValue(f->getOperand());
    auto builder = assignment.getBuilder(f->getIterator());
    builder.createFixLifetime(f->getLoc(), addr);
    assignment.markForDeletion(f);
  }

  void visitUncheckedTrivialBitCastInst(UncheckedTrivialBitCastInst *bc) {
    auto addr = assignment.getAddressForValue(bc->getOperand());
    auto builder = assignment.getBuilder(bc->getIterator());
    auto loaded = builder.createLoad(bc->getLoc(), addr,
                                     LoadOwnershipQualifier::Unqualified);
    auto newVal = builder.createUncheckedTrivialBitCast(bc->getLoc(), loaded,
                                                        bc->getType());
    bc->replaceAllUsesWith(newVal);
    assignment.markForDeletion(bc);
  }

  void visitUncheckedBitwiseCastInst(UncheckedBitwiseCastInst *bc) {
    auto addr = assignment.getAddressForValue(bc->getOperand());
    auto builder = assignment.getBuilder(bc->getIterator());
    auto loaded = builder.createLoad(bc->getLoc(), addr,
                                     LoadOwnershipQualifier::Unqualified);
    auto newVal = builder.createUncheckedBitwiseCast(bc->getLoc(), loaded,
                                                     bc->getType());
    bc->replaceAllUsesWith(newVal);
    assignment.markForDeletion(bc);
  }

  void visitEnumInst(EnumInst *e) {
    assert(!assignment.isLargeLoadableType(e->getType()));
    auto opd = e->getOperand();
    auto opdAddr = assignment.getAddressForValue(opd);
    auto builder = assignment.getBuilder(e->getIterator());
    auto enumAddr = assignment.createAllocStack(e->getType());
    auto initAddr = builder.createInitEnumDataAddr(
        assignment.getAutoLoc(), enumAddr, e->getElement(),
        opd->getType().getAddressType());
    builder.createCopyAddr(e->getLoc(), opdAddr, initAddr, IsTake,
                           IsInitialization);
    builder.createInjectEnumAddr(e->getLoc(), enumAddr, e->getElement());
    auto enumVal = builder.createLoad(e->getLoc(), enumAddr,
                                      LoadOwnershipQualifier::Unqualified);
    e->replaceAllUsesWith(enumVal);
    assignment.markForDeletion(e);
  }

  void visitApplySite(ApplySite apply) {
    for (auto &opd : apply.getArgumentOperands()) {
      if (assignment.contains(opd.get())) {
        auto builder = assignment.getBuilder(apply->getIterator());
        auto loaded = builder.createLoad(
            apply->getLoc(), assignment.getAddressForValue(opd.get()),
            LoadOwnershipQualifier::Unqualified);
        opd.set(loaded);
      }
    }
  }

  void visitApplyInst(ApplyInst *apply) { visitApplySite(apply); }

  void visitBeginApplyInst(BeginApplyInst *apply) { visitApplySite(apply); }

  void visitTryApplyInst(TryApplyInst *apply) { visitApplySite(apply); }

  void visitPartialApplyInst(PartialApplyInst *apply) { visitApplySite(apply); }

  void visitReturnInst(ReturnInst *r) {
    auto builder = assignment.getBuilder(r->getIterator());
    auto opdAddr = assignment.getAddressForValue(r->getOperand());
    auto newVal = builder.createLoad(assignment.getAutoLoc(), opdAddr,
                                     LoadOwnershipQualifier::Unqualified);
    auto termBuilder = assignment.getTermBuilder(r);
    termBuilder.createReturn(r->getLoc(), newVal);
    assignment.markForDeletion(r);
  }

  void visitMarkDependenceInst(MarkDependenceInst *m) {
    auto builder = assignment.getBuilder(m->getIterator());
    auto opdAddr = assignment.getAddressForValue(m->getBase());
    auto newValue = builder.createMarkDependence(m->getLoc(), m->getValue(),
                                                 opdAddr, m->dependenceKind());
    m->replaceAllUsesWith(newValue);
    assignment.markForDeletion(m);
  }

  void visitStoreInst(StoreInst *store) {
    auto builder = assignment.getBuilder(store->getIterator());
    SILValue addr = assignment.getAddressForValue(store->getSrc());
    builder.createCopyAddr(store->getLoc(), addr, store->getDest(), IsTake,
                           IsInitialization);
    assignment.markForDeletion(store);
  }

  bool overlapsWithOnStackDebugLoc(SILValue v) {
    for (auto *use : v->getUses()) {
      auto *store = dyn_cast<StoreInst>(use->getUser());
      if (!store)
        continue;

      auto *stackLoc = dyn_cast<AllocStackInst>(store->getDest());
      if (!stackLoc)
        continue;
      if (getAnyDebugUse(stackLoc))
        return true;
    }
    return false;
  }

  void visitDebugValueInst(DebugValueInst *dbg) {
    for (auto &operand : dbg->getAllOperands()) {
      SILValue operandVal = operand.get();
      // The same conditions as in `AddressAssignment::assign` need to be
      // done for each operand: rewrite all operands that need rewriting.
      if (!operandVal->getType().isObject())
        continue;
      if (!assignment.isLargeLoadableType(operandVal->getType()))
        continue;
      // Undef debug values have a special meaning. Don't allocate stack space
      // for those.
      // Rewriting them would also break nullary debug reconstruction blocks.
      if (isa<SILUndef>(operandVal))
        continue;
      if (!operandVal->getType().isAddress() &&
          (assignment.isPotentiallyCArray(operandVal->getType()) ||
           overlapsWithOnStackDebugLoc(operandVal))) {
        assignment.markForDeletion(dbg);
        return;
      }
      SILValue addr = assignment.getAddressForValue(operandVal);
      operand.set(addr);
      dbg->prependDeref(operand.getOperandNumber());
    }
  }

  void visitRetainValueInst(RetainValueInst *r) {
    auto builder = assignment.getBuilder(r->getIterator());
    SILValue addr = assignment.getAddressForValue(r->getOperand());
    builder.createRetainValueAddr(r->getLoc(), addr, r->getAtomicity());
    assignment.markForDeletion(r);
  }

  void visitReleaseValueInst(ReleaseValueInst *r) {
    auto builder = assignment.getBuilder(r->getIterator());
    SILValue addr = assignment.getAddressForValue(r->getOperand());
    builder.createReleaseValueAddr(r->getLoc(), addr, r->getAtomicity());
    assignment.markForDeletion(r);
  }

  void visitSelectEnumInst(SelectEnumInst *select) {
    auto builder = assignment.getBuilder(select->getIterator());
    SmallVector<std::pair<EnumElementDecl *, SILValue>> caseValues;
    for (unsigned idx = 0, cnt = select->getNumCases(); idx < cnt; ++idx) {
      caseValues.push_back(select->getCase(idx));
    }
    SILValue opAddr = assignment.getAddressForValue(select->getOperand());
    SILValue defaultValue;
    if (select->hasDefault())
      defaultValue = select->getDefaultResult();
    auto res = builder.createSelectEnumAddr(
        select->getLoc(), opAddr, select->getType(), defaultValue, caseValues);
    select->replaceAllUsesWith(res);
    assignment.markForDeletion(select);
  }

  void visitStructExtractInst(StructExtractInst *extract) {
    if (isa<SILUndef>(extract->getOperand()))
      return;

    auto builder = assignment.getBuilder(extract->getIterator());
    auto opdAddr = assignment.getAddressForValue(extract->getOperand());
    auto projAddr = builder.createStructElementAddr(
        extract->getLoc(), opdAddr, extract->getField(),
        extract->getType().getAddressType());
    auto load = builder.createLoad(extract->getLoc(), projAddr,
                                   LoadOwnershipQualifier::Unqualified);
    extract->replaceAllUsesWith(load);
    assignment.markForDeletion(extract);
  }

  void visitTupleExtractInst(TupleExtractInst *extract) {
    auto builder = assignment.getBuilder(extract->getIterator());
    auto opdAddr = assignment.getAddressForValue(extract->getOperand());
    auto projAddr = builder.createTupleElementAddr(
        extract->getLoc(), opdAddr, extract->getFieldIndex(),
        extract->getType().getAddressType());
    auto load = builder.createLoad(extract->getLoc(), projAddr,
                                   LoadOwnershipQualifier::Unqualified);
    extract->replaceAllUsesWith(load);
    assignment.markForDeletion(extract);
  }

  void visitSwitchEnumInst(SwitchEnumInst *sw) {
    auto opdAddr = assignment.getAddressForValue(sw->getOperand());
    // UncheckedTakeEnumDataAddr is destructive. If we have a used switch target
    // block argument we need to provide for a destructible location for the
    // UncheckedTakeEnumDataAddr.
    SILValue destructibleAddress;
    auto initDestructibleAddress = [&] () -> void{
      if (destructibleAddress)
        return;
      auto addr = assignment.createAllocStack(sw->getOperand()->getType());
      // UncheckedTakeEnumDataAddr is destructive.
      // So we need to copy to keep the original address location valid.
      auto builder = assignment.getBuilder(sw->getIterator());
      builder.createCopyAddr(sw->getLoc(), opdAddr, addr, IsTake,
                             IsInitialization);
      destructibleAddress = addr;
    };

    auto loc = sw->getLoc();

    auto rewriteCase = [&](EnumElementDecl *caseDecl, SILBasicBlock *caseBB) {
      // Nothing to do for unused case payloads.
      if (caseBB->getArguments().size() == 0 ||
          caseBB->getArguments()[0]->use_empty()) {
        if (caseBB->getArguments().size()) {
          assignment.markBlockArgumentForDeletion(caseBB);
        }
        return;
      }

      initDestructibleAddress();

      assert(caseBB->getArguments().size() == 1);
      SILArgument *caseArg = caseBB->getArgument(0);
      assert(caseDecl->hasAssociatedValues() &&
             "caseBB has a payload argument");

      SILBuilder caseBuilder = assignment.getBuilder(caseBB->begin());
      auto *caseAddr =
        caseBuilder.createUncheckedEnumDataAddrForTake(loc, destructibleAddress,
                                           caseDecl,
                                           caseArg->getType().getAddressType());

      if (assignment.isLargeLoadableType(caseArg->getType())) {
        assignment.mapValueToAddress(caseArg, caseAddr);
        assignment.markBlockArgumentForDeletion(caseBB);
      } else {
        auto *caseLoad = caseBuilder.createLoad(
            loc, caseAddr, LoadOwnershipQualifier::Unqualified);
        caseArg->replaceAllUsesWith(caseLoad);
        caseBB->eraseArgument(0);
      }
    };

    SmallVector<std::pair<EnumElementDecl *, SILBasicBlock *>, 8> cases;
    SmallVector<ProfileCounter, 8> caseCounters;

    // Collect switch cases for rewriting and remove block arguments.
    for (unsigned caseIdx : range(sw->getNumCases())) {
      auto caseDeclAndBB = sw->getCase(caseIdx);
      EnumElementDecl *caseDecl = caseDeclAndBB.first;
      SILBasicBlock *caseBB = caseDeclAndBB.second;

      cases.push_back(caseDeclAndBB);
      caseCounters.push_back(sw->getCaseCount(caseIdx));

      rewriteCase(caseDecl, caseBB);
    }

    SILBasicBlock *defaultBB = nullptr;
    auto defaultCounter = ProfileCounter();
    if (sw->hasDefault()) {
      defaultBB = sw->getDefaultBB();
      defaultCounter = sw->getDefaultCount();
      if (auto defaultDecl = sw->getUniqueCaseForDefault()) {
        rewriteCase(defaultDecl.get(), defaultBB);
      } else if (defaultBB->getNumArguments() == 0) {
        // nothing to do there.
      } else {
        SILArgument *arg = defaultBB->getArgument(0);
#ifndef NDEBUG
        arg->dump();
        sw->dump();
#endif
        llvm::report_fatal_error("Unhandle switch_enum");
      }
    }
    auto builder = assignment.getTermBuilder(sw);
    builder.createSwitchEnumAddr(loc, opdAddr, defaultBB, cases,
                                 ArrayRef<ProfileCounter>(caseCounters),
                                 defaultCounter);
    sw->eraseFromParent();
  }

  void visitUncheckedEnumDataInst(UncheckedEnumDataInst *enumData) {
    auto builder = assignment.getBuilder(enumData->getIterator());
    auto opdAddr = assignment.getAddressForValue(enumData->getOperand());
    auto loc = enumData->getLoc();

    // UncheckedTakeEnumDataAddrInst is destructive.
    auto addr = assignment.createAllocStack(enumData->getOperand()->getType());
    builder.createCopyAddr(enumData->getLoc(), opdAddr, addr, IsTake,
                           IsInitialization);
    // TODO: we could omit the copy if this is the only user of the operand,
    // or we can use a nondestructive in-place projection.
    auto extractAddr = builder.createUncheckedEnumDataAddrForTake(
        loc, addr, enumData->getElement(),
        enumData->getType().getAddressType());
    auto newVal = builder.createLoad(loc, extractAddr,
                                     LoadOwnershipQualifier::Unqualified);
    enumData->replaceAllUsesWith(newVal);
    assignment.markForDeletion(enumData);
  }

  void visitBranchInst(BranchInst *br) {
    SmallVector<SILValue, 8> newArgs;
    auto *succBB = br->getParent()->getSingleSuccessorBlock();
    unsigned idx = 0;
    // We need to perform a parallel copy of the phi arguments (because of
    // cycles)
    // First collect the sources of the copy.
    llvm::DenseSet<SILValue> oldSources;
    for (auto arg : br->getArgs()) {
      auto ty = arg->getType();
      if (!assignment.isLargeLoadableType(ty)) {
        idx++;
        continue;
      }
      auto addr = assignment.getAddressForValue(arg);
      oldSources.insert(addr);
      idx++;
    }

    // Remap (copy) any source that is also a destination to a new stack
    // location.
    llvm::DenseMap<SILValue, SILValue> remappedSource;
    idx = 0;
    for (auto arg : br->getArgs()) {
      auto ty = arg->getType();
      if (!assignment.isLargeLoadableType(ty)) {
        idx++;
        continue;
      }
      auto destAddr = assignment.getAddressForValue(succBB->getArgument(idx));
      if (oldSources.count(destAddr)) {
        auto newSrc = assignment.createAllocStack(destAddr->getType());
        auto builder = assignment.getBuilder(br->getIterator());
        builder.createCopyAddr(assignment.getAutoLoc(), destAddr, newSrc,
                               IsTake, IsInitialization);
        remappedSource[destAddr] = newSrc;
      }
      idx++;
    }

    idx = 0;
    for (auto arg : br->getArgs()) {
      auto ty = arg->getType();
      if (!assignment.isLargeLoadableType(ty)) {
        newArgs.push_back(arg);
        idx++;
        continue;
      }
      auto addr = assignment.getAddressForValue(arg);

      // Use the remapped source location if necessary.
      if (remappedSource.find(addr) != remappedSource.end()) {
        addr = remappedSource[addr];
      }
      auto builder = assignment.getBuilder(br->getIterator());
      auto destAddr = assignment.getAddressForValue(succBB->getArgument(idx));
      builder.createCopyAddr(assignment.getAutoLoc(), addr, destAddr, IsTake,
                             IsInitialization);
      idx++;
    }

    auto builder = assignment.getTermBuilder(br);
    builder.createBranch(br->getLoc(), br->getDestBB(), newArgs);
    assignment.markForDeletion(br);
  }

  void visitValueMetatypeInst(ValueMetatypeInst *metatype) {
    auto builder = assignment.getBuilder(metatype->getIterator());
    auto opdAddr = assignment.getAddressForValue(metatype->getOperand());
    auto loc = metatype->getLoc();
    auto loaded =
        builder.createLoad(loc, opdAddr, LoadOwnershipQualifier::Unqualified);
    auto newVal = builder.createValueMetatype(loc, metatype->getType(), loaded);
    metatype->replaceAllUsesWith(newVal);
    assignment.markForDeletion(metatype);
  }
};
} // namespace

AllocStackInst *AddressAssignment::createAllocStack(SILType ty) {
  auto insertPt = currFn.getEntryBlock()->front().getIterator();
  auto builder = getBuilder(insertPt);
  auto as = builder.createAllocStack(getAutoLoc(), ty);
  allocStacks.push_back(as);
  return as;
}

void AddressAssignment::assign(SILInstruction *inst) {

  bool rewritten = false;

  // Rewrite definitions to addresses.
  for (auto val : inst->getResults()) {
    auto ty = val->getType();
    if (!ty.isObject())
      continue;

    if (!isLargeLoadableType(ty))
      continue;

    AssignAddressToDef a(*this, val);
    a.rewriteValue();
    rewritten = true;
  }

  if (rewritten)
    return;

  // Rewrite users to use addresses.
  for (auto &opd : inst->getAllOperands()) {
    SILValue val = opd.get();
    auto ty = val->getType();
    if (!ty.isObject())
      continue;
    if (!isLargeLoadableType(ty))
      continue;

    RewriteUser r(*this, inst);
    r.rewrite();
    return;
  }
}

static void runPeepholesAndReg2Mem(SILPassManager *pm, SILModule *silMod,
                                   IRGenModule *irgenModule) {

  if (!irgenModule->getOptions().EnableLargeLoadableTypesReg2Mem)
    return;

  for (SILFunction &currF : *silMod) {
    if (!currF.isDefinition())
      continue;

    GenericEnvironment *genEnv = getSubstGenericEnvironment(&currF);

    removeUnreachableBlocks(currF);

    // copy_addr peepholes
    bool UseAggressiveHeuristic =
      silMod->getOptions().UseAggressiveReg2MemForCodeSize;
    LargeLoadableHeuristic heuristic(irgenModule, genEnv, currF,
                                     UseAggressiveHeuristic);
    Peepholes opts(pm, silMod, irgenModule);
    for (SILBasicBlock &BB : currF) {
      for (auto *arg : BB.getArguments()) {
        heuristic.visit(arg);
      }
      for (SILInstruction &I : BB) {
        heuristic.visit(&I);
        if (opts.ignore(&I))
          continue;

        if (opts.optimizeLoad(BB, &I))
          continue;
      }
    }
    // Delete replaced instructions.
    opts.deleteInstructions();

    PostOrderFunctionInfo postorderInfo(&currF);
    heuristic.propagate(postorderInfo);

    AddressAssignment assignment(heuristic, irgenModule, currF);

    // Assign addresses to basic block arguments.

    // Store alloc_stack's we created that need to be initialized after we
    // proccess the original instructions in the function.
    SmallVector<std::pair<AllocStackInst *, SILValue>, 8> largeArguments;
    SmallVector<std::pair<AllocStackInst *, SILArgument *>, 8>
        largeTryApplyResults;

    auto *entryBB = currF.getEntryBlock();
    for (auto &bb : currF) {
      // Assign addresses for large entry block arguments.
      if (&bb == entryBB) {
        for (auto *arg : bb.getArguments()) {
          auto ty = arg->getType();
          if (assignment.isLargeLoadableType(ty)) {
            auto addr = assignment.createAllocStack(ty);
            assignment.mapValueToAddress(arg, addr);
            // We will emit the store to initialize after parsing all other
            // instructions so that we don't accidentally rewrite it to an
            // by-address insttruction.
            largeArguments.push_back(std::make_pair(addr, SILValue(arg)));
          }
        }
        continue;
      }

      // Switch enum successor blocks are handle when processing the
      // switch_enum.
      if (auto *pred = bb.getSinglePredecessorBlock()) {
        // switch_enum handles the basic block arguments independently.
        if (auto sw = dyn_cast<SwitchEnumInst>(pred->getTerminator())) {
          if (assignment.isLargeLoadableType(sw->getOperand()->getType())) {
              continue;
          }
        }
      }

      // Handle all other basic block arguments.
      for (auto *arg : bb.getArguments()) {
        auto ty = arg->getType();
        if (assignment.isLargeLoadableType(ty)) {
          auto addr = assignment.createAllocStack(ty);
          assignment.mapValueToAddress(arg, addr);

          bool shouldDeleteBlockArgument = true;

          // Large try apply results have to be stored to their stack location
          // in the success block.
          if (auto *pred = bb.getSinglePredecessorBlock()) {
            if (isa<TryApplyInst>(pred->getTerminator()) ||
                isa<SwitchEnumInst>(pred->getTerminator()) ||
                isa<CheckedCastBranchInst>(pred->getTerminator())) {
              assert(bb.getArguments().size() == 1);
              shouldDeleteBlockArgument = false;
              // We will emit the store to initialize after parsing all other
              // instructions so that we don't accidentally rewrite it to an
              // by-address insttruction.
              largeTryApplyResults.push_back(std::make_pair(addr, arg));
            }
          }

          if (shouldDeleteBlockArgument)
            assignment.markBlockArgumentForDeletion(&bb, arg->getIndex());
        }
      }
    }

    // Asign addresses to non-address SSA values.
    for (auto *BB : postorderInfo.getReversePostOrder()) {
      SmallVector<SILInstruction *, 32> origInsts;
      for (SILInstruction &i : *BB) {
        origInsts.push_back(&i);
      }
      for (SILInstruction *i : origInsts)
        assignment.assign(i);
    }

    // Emit stores for large arguments to their address location.
    for (auto largeArgPair : largeArguments) {
      auto *addr = largeArgPair.first;
      auto arg = largeArgPair.second;
      auto builder = assignment.getBuilder(++addr->getIterator());
      builder.createStore(assignment.getAutoLoc(), arg, addr,
                          StoreOwnershipQualifier::Unqualified);
    }

    // Emit stores for large results to their address location.
    for (auto largeResultPair : largeTryApplyResults) {
      auto *addr = largeResultPair.first;
      auto *arg = largeResultPair.second;
      auto *bb = arg->getParent();
      auto builder = assignment.getBuilder(bb->begin());
      builder.createStore(assignment.getAutoLoc(), arg, addr,
                          StoreOwnershipQualifier::Unqualified);
    }

    auto *dominance = pm->getAnalysis<DominanceAnalysis>();
    auto *deadEnds = pm->getAnalysis<DeadEndBlocksAnalysis>();
    assignment.finish(dominance->get(&currF), deadEnds->get(&currF));
    pm->invalidateAnalysis(
        &currF, SILAnalysis::InvalidationKind::BranchesAndInstructions);
  }
}

SILTransform *irgen::createLoadableByAddress() {
  return new LoadableByAddress();
}
