/// Derive a concrete type of self and conformance from the init_existential
/// instruction.
static Optional<std::tuple<ProtocolConformanceRef, CanType, SILValue>>
        getConformanceAndConcreteType(FullApplySite AI,
                                      SILInstruction *InitExistential,
                                      ProtocolDecl *Protocol,
                                      SILValue &NewSelf,
ArrayRef<ProtocolConformanceRef> &Conformances) {
    // Try to derive the concrete type of self from the found init_existential.
    CanType ConcreteType;
    SILValue ConcreteTypeDef;
    if (auto IE = dyn_cast<InitExistentialAddrInst>(InitExistential)) {
        Conformances = IE->getConformances();
        ConcreteType = IE->getFormalConcreteType();
        NewSelf = IE;
    } else if (auto IER = dyn_cast<InitExistentialRefInst>(InitExistential)) {
        Conformances = IER->getConformances();
        ConcreteType = IER->getFormalConcreteType();
        NewSelf = IER->getOperand();
    } else if (auto IEM = dyn_cast<InitExistentialMetatypeInst>(InitExistential)) {
        Conformances = IEM->getConformances();
        NewSelf = IEM->getOperand();
        ConcreteType = NewSelf->getType().getSwiftRValueType();

        auto ExType = IEM->getType().getSwiftRValueType();
        while (auto ExMetatype = dyn_cast<ExistentialMetatypeType>(ExType)) {
            ExType = ExMetatype.getInstanceType();
            ConcreteType = cast<MetatypeType>(ConcreteType).getInstanceType();
        }
    } else {
        return None;
    }

    if (ConcreteType->isOpenedExistential()) {
        assert(!InitExistential->getTypeDependentOperands().empty() &&
               "init_existential is supposed to have a typedef operand");
        ConcreteTypeDef = InitExistential->getTypeDependentOperands()[0].get();
    }

    // Find the conformance for the protocol we're interested in.
    for (auto Conformance : Conformances) {
        auto Requirement = Conformance.getRequirement();
        if (Requirement == Protocol) {
            return std::make_tuple(Conformance, ConcreteType, ConcreteTypeDef);
        }
        if (Requirement->inheritsFrom(Protocol)) {
            // If Requirement != Protocol, then the abstract conformance cannot be used
            // as is and we need to create a proper conformance.
            return std::make_tuple(Conformance.isAbstract()
                                   ? ProtocolConformanceRef(Protocol)
                                   : Conformance,
                                   ConcreteType, ConcreteTypeDef);
        }
    }

    llvm_unreachable("couldn't find matching conformance in substitution?");
}
/// Find an opened archetype represented by this type.
/// It is assumed by this method that the type contains
/// at most one opened archetype.
/// Typically, it would be called from a type visitor.
/// It checks only the type itself, but does not try to
/// recursively check any children of this type, because
/// this is the task of the type visitor invoking it.
/// \returns The found archetype or empty type otherwise.
CanType swift::getOpenedArchetypeOf(CanType Ty) {
  if (!Ty)
    return CanType();
  assert(hasAtMostOneOpenedArchetype(Ty) &&
         "Type should contain at most one opened archetype");
  while (auto MetaTy = dyn_cast<AnyMetatypeType>(Ty)) {
    Ty = MetaTy->getInstanceType().getCanonicalTypeOrNull();
  }
  if (Ty->isOpenedExistential())
    return Ty;
  return CanType();
}
/// Propagate information about a concrete type from init_existential_addr
/// or init_existential_ref into witness_method conformances and into
/// apply instructions.
/// This helps the devirtualizer to replace witness_method by
/// class_method instructions and then devirtualize.
SILInstruction *
SILCombiner::propagateConcreteTypeOfInitExistential(FullApplySite AI,
        ProtocolDecl *Protocol,
        llvm::function_ref<void(CanType , ProtocolConformanceRef)> Propagate) {

    // Get the self argument.
    SILValue Self;
    if (auto *Apply = dyn_cast<ApplyInst>(AI)) {
        if (Apply->hasSelfArgument())
            Self = Apply->getSelfArgument();
    } else if (auto *Apply = dyn_cast<TryApplyInst>(AI)) {
        if (Apply->hasSelfArgument())
            Self = Apply->getSelfArgument();
    }

    assert(Self && "Self argument should be present");

    // Try to find the init_existential, which could be used to
    // determine a concrete type of the self.
    CanType OpenedArchetype;
    SILValue OpenedArchetypeDef;
    SILInstruction *InitExistential =
        findInitExistential(AI, Self, OpenedArchetype, OpenedArchetypeDef);
    if (!InitExistential)
        return nullptr;

    // Try to derive the concrete type of self and a related conformance from
    // the found init_existential.
    ArrayRef<ProtocolConformanceRef> Conformances;
    auto NewSelf = SILValue();
    auto ConformanceAndConcreteType =
        getConformanceAndConcreteType(AI, InitExistential,
                                      Protocol, NewSelf, Conformances);
    if (!ConformanceAndConcreteType)
        return nullptr;

    ProtocolConformanceRef Conformance = std::get<0>(*ConformanceAndConcreteType);
    CanType ConcreteType = std::get<1>(*ConformanceAndConcreteType);
    SILValue ConcreteTypeDef = std::get<2>(*ConformanceAndConcreteType);

    SILOpenedArchetypesTracker *OldOpenedArchetypesTracker =
        Builder.getOpenedArchetypesTracker();

    SILOpenedArchetypesTracker OpenedArchetypesTracker(*AI.getFunction());

    if (ConcreteType->isOpenedExistential()) {
        // Prepare a mini-mapping for opened archetypes.
        // SILOpenedArchetypesTracker OpenedArchetypesTracker(*AI.getFunction());
        OpenedArchetypesTracker.addOpenedArchetypeDef(ConcreteType, ConcreteTypeDef);
        Builder.setOpenedArchetypesTracker(&OpenedArchetypesTracker);
    }

    // Propagate the concrete type into the callee-operand if required.
    Propagate(ConcreteType, Conformance);

    // Create a new apply instruction that uses the concrete type instead
    // of the existential type.
    auto *NewAI = createApplyWithConcreteType(AI, NewSelf, Self, ConcreteType,
                  ConcreteTypeDef, Conformance,
                  OpenedArchetype);

    if (ConcreteType->isOpenedExistential())
        Builder.setOpenedArchetypesTracker(OldOpenedArchetypesTracker);

    return NewAI;
}