//===--- CastOptimizer.cpp ------------------------------------------------===// // // This source file is part of the Swift.org open source project // // Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See https://swift.org/LICENSE.txt for license information // See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// /// /// \file /// /// This file contains local cast optimizations and simplifications. /// //===----------------------------------------------------------------------===// #include "swift/SILOptimizer/Utils/CastOptimizer.h" #include "swift/AST/ConformanceLookup.h" #include "swift/AST/ExistentialLayout.h" #include "swift/AST/GenericSignature.h" #include "swift/AST/Module.h" #include "swift/AST/SubstitutionMap.h" #include "swift/Basic/Assertions.h" #include "swift/SIL/BasicBlockUtils.h" #include "swift/SIL/DebugUtils.h" #include "swift/SIL/DynamicCasts.h" #include "swift/SIL/InstructionUtils.h" #include "swift/SIL/SILArgument.h" #include "swift/SIL/SILBuilder.h" #include "swift/SIL/SILModule.h" #include "swift/SIL/SILUndef.h" #include "swift/SIL/TypeLowering.h" #include "swift/SILOptimizer/Analysis/ARCAnalysis.h" #include "swift/SILOptimizer/Analysis/Analysis.h" #include "swift/SILOptimizer/Analysis/DominanceAnalysis.h" #include "swift/SILOptimizer/Utils/CFGOptUtils.h" #include "swift/SILOptimizer/Utils/InstOptUtils.h" #include "swift/SILOptimizer/Utils/SILOptFunctionBuilder.h" #include "llvm/ADT/SmallPtrSet.h" #include "llvm/ADT/StringSwitch.h" #include "llvm/IR/Intrinsics.h" #include "llvm/Support/CommandLine.h" #include "llvm/Support/Compiler.h" #include using namespace swift; //===----------------------------------------------------------------------===// // ObjC -> Swift Bridging Cast Optimization //===----------------------------------------------------------------------===// static SILFunction * getObjCToSwiftBridgingFunction(SILOptFunctionBuilder &funcBuilder, SILDynamicCastInst dynamicCast) { // inline constructor. auto *bridgeFuncDecl = [&]() -> FuncDecl * { auto &astContext = dynamicCast.getModule().getASTContext(); if (dynamicCast.isConditional()) { return astContext.getConditionallyBridgeFromObjectiveCBridgeable(); } return astContext.getForceBridgeFromObjectiveCBridgeable(); }(); assert(bridgeFuncDecl && "Bridging function doesn't exist?!"); SILDeclRef funcDeclRef(bridgeFuncDecl, SILDeclRef::Kind::Func); // Lookup a function from the stdlib. return funcBuilder.getOrCreateFunction(dynamicCast.getLocation(), funcDeclRef, ForDefinition_t::NotForDefinition); } static SubstitutionMap lookupBridgeToObjCProtocolSubs(CanType target) { auto bridgedProto = target->getASTContext().getProtocol(KnownProtocolKind::ObjectiveCBridgeable); auto conf = lookupConformance(target, bridgedProto); return SubstitutionMap::getProtocolSubstitutions(conf); } /// Given that our insertion point is at the cast that we are trying to /// optimize, convert our incoming value to something that can be passed to the /// bridge call. static std::pair convertObjectToLoadableBridgeableType(SILBuilderWithScope &builder, SILDynamicCastInst dynamicCast, SILValue src) { auto *f = dynamicCast.getFunction(); auto loc = dynamicCast.getLocation(); bool isConditional = dynamicCast.isConditional(); SILValue load = builder.emitLoadValueOperation(loc, src, LoadOwnershipQualifier::Take); SILType silBridgedTy = *dynamicCast.getLoweredBridgedTargetObjectType(); // If we are not conditional... if (!isConditional) { // and our loaded type is our bridged type, just return the load as our // SILValue and signal to our caller that we did not create a new cast // instruction by returning nullptr as second. if (load->getType() == silBridgedTy) { return {load, nullptr}; } // Otherwise, just perform an unconditional checked cast to the sil bridged // ty. We return the cast as our value and as our new cast instruction. auto *cast = builder.createUnconditionalCheckedCast( loc, dynamicCast.getCheckedCastOptions(), load, silBridgedTy, dynamicCast.getBridgedTargetType()); return {cast, cast}; } SILBasicBlock *castSuccessBB = f->createBasicBlockAfter(dynamicCast.getInstruction()->getParent()); castSuccessBB->createPhiArgument(silBridgedTy, OwnershipKind::Owned); // If we /are/ conditional and we do not need to bridge the load to the sil, // then we just create our cast success block and branch from the end of the // cast instruction block to the cast success block. We leave our insertion // point in the cast success block since when we return, we are going to // insert the bridge call/switch there. We return the argument of the cast // success block as the value to be passed to the bridging function. if (load->getType() == silBridgedTy) { f->moveBlockAfter(castSuccessBB, dynamicCast.getInstruction()->getParent()); builder.createBranch(loc, castSuccessBB, load); builder.setInsertionPoint(castSuccessBB); return {castSuccessBB->getArgument(0), nullptr}; } auto *castFailBB = ([&]() -> SILBasicBlock * { auto *failureBB = dynamicCast.getFailureBlock(); SILBuilderWithScope failureBBBuilder(&(*failureBB->begin()), builder); return splitBasicBlockAndBranch(failureBBBuilder, &(*failureBB->begin()), nullptr, nullptr); }()); // Now that we have created the failure bb, move our cast success block right // after the checked_cast_br bb. f->moveBlockAfter(castSuccessBB, dynamicCast.getInstruction()->getParent()); // Ok, we need to perform the full cast optimization. This means that we are // going to replace the cast terminator in inst_block with a checked_cast_br. auto *ccbi = builder.createCheckedCastBranch(loc, false, dynamicCast.getCheckedCastOptions(), load, dynamicCast.getBridgedSourceType(), silBridgedTy, dynamicCast.getBridgedTargetType(), castSuccessBB, castFailBB); splitEdge(ccbi, /* EdgeIdx to CastFailBB */ 1); // Now that we have split the edge to cast fail bb, add the default argument // for the checked_cast_br. Then we need to handle our error conditions, // namely we destroy on take_always and otherwise store the value back into // the memory location that we took it out of. { auto *newFailureBlock = ccbi->getFailureBB(); SILValue defaultArg; if (builder.hasOwnership()) { defaultArg = newFailureBlock->createPhiArgument(load->getType(), OwnershipKind::Owned); } else { defaultArg = ccbi->getOperand(); } // This block should be properly terminated already due to our method of // splitting the failure block, so we can use begin() safely. SILBuilderWithScope failureBuilder(newFailureBlock->begin()); switch (dynamicCast.getBridgedConsumptionKind()) { case CastConsumptionKind::TakeAlways: failureBuilder.emitDestroyValueOperation(loc, defaultArg); break; case CastConsumptionKind::TakeOnSuccess: case CastConsumptionKind::CopyOnSuccess: // Without ownership, we do not need to consume the taken value. if (failureBuilder.hasOwnership()) { failureBuilder.emitStoreValueOperation(loc, defaultArg, src, StoreOwnershipQualifier::Init); } break; case CastConsumptionKind::BorrowAlways: llvm_unreachable("this should never occur here"); } } builder.setInsertionPoint(castSuccessBB); return {castSuccessBB->getArgument(0), ccbi}; } /// Create a call of _forceBridgeFromObjectiveC_bridgeable or /// _conditionallyBridgeFromObjectiveC_bridgeable which converts an ObjC /// instance into a corresponding Swift type, conforming to /// _ObjectiveCBridgeable. /// /// Control Flow Modification Model /// =============================== /// /// NOTE: In the following we assume that our src type is not address only. We /// do not support optimizing such source types today. /// /// Unconditional Casts /// ------------------- /// /// In the case of unconditional casts, we do not touch the CFG at all. We /// perform the following optimizations: /// /// 1. If the bridged type and the src type equal, we replace the cast with the /// apply. /// /// 2. If src is an address and bridged type has the matching object type to /// src, just load the value and again replace the cast with the apply. /// /// 3. If src is an address and after loading still doesn't match bridged type, /// insert an unconditional_checked_cast before calling the apply. /// /// Conditional Casts /// ----------------- /// /// In the case of a conditional const (i.e. checked_cast_addr_br), we transform /// the following CFG: /// /// ``` /// InstBlock (checked_cast_addr_br) -> FailureBB -> FailureSucc /// \ /// \----------------------------> SuccessBB -> SuccessSucc /// ``` /// /// to a CFG of the following form: /// /// ``` /// InstBlock (checked_cast_br) -> CastFailBB -> FailureBB -> FailureSucc /// | ^ /// \-> CastSuccessBB (bridge call + switch) --| /// | /// \-> BridgeSuccessBB -> SuccessBB -> SuccessSucc /// ``` /// /// NOTE: That if the underlying src type matches the type of the underlying /// bridge source object, we can omit the initial checked_cast_br and just load /// the value + branch to the CastSuccessBB. This results instead in the /// following CFG: /// /// ``` /// InstBlock (br) FailureBB -> FailureSucc /// | ^ /// \-> CastSuccessBB (bridge call + switch) --| /// | /// \-> BridgeSuccessBB -> SuccessBB -> SuccessSucc /// ``` /// SILInstruction * CastOptimizer::optimizeBridgedObjCToSwiftCast(SILDynamicCastInst dynamicCast) { auto kind = dynamicCast.getKind(); (void)kind; assert(((kind == SILDynamicCastKind::CheckedCastAddrBranchInst) || (kind == SILDynamicCastKind::UnconditionalCheckedCastAddrInst)) && "Unsupported dynamic cast kind"); CanType target = dynamicCast.getTargetFormalType(); auto &mod = dynamicCast.getModule(); // AnyHashable is a special case that we do not handle since we only handle // objc targets in this function. Bailout early. if (target->isAnyHashable()) { return nullptr; } SILValue src = dynamicCast.getSource(); SILInstruction *Inst = dynamicCast.getInstruction(); auto *F = Inst->getFunction(); // Check if we have a source type that is address only. We do not support that // today. if (src->getType().isAddressOnly(*F)) { return nullptr; } bool isConditional = dynamicCast.isConditional(); SILValue Dest = dynamicCast.getDest(); SILBasicBlock *SuccessBB = dynamicCast.getSuccessBlock(); SILBasicBlock *FailureBB = dynamicCast.getFailureBlock(); auto Loc = Inst->getLoc(); // The conformance to _BridgedToObjectiveC is statically known. // Retrieve the bridging operation to be used if a static conformance // to _BridgedToObjectiveC can be proven. SILFunction *bridgingFunc = getObjCToSwiftBridgingFunction(functionBuilder, dynamicCast); if (!bridgingFunc) return nullptr; auto paramTypes = bridgingFunc->getLoweredFunctionType()->getParameters(); (void)paramTypes; assert(paramTypes[0].getConvention() == ParameterConvention::Direct_Guaranteed && "Parameter should be @guaranteed"); SILBuilderWithScope Builder(Inst, builderContext); // Generate a load for the source argument since as part of our optimization // we are going to promote the cast to work with objects instead of // addresses. Additionally, if we have an objc object that is not bridgeable, // but that could be converted to something that is bridgeable, we try to // convert to the bridgeable type. SILValue srcOp; SILInstruction *newI; std::tie(srcOp, newI) = convertObjectToLoadableBridgeableType(Builder, dynamicCast, src); // Now emit the a cast from the casted ObjC object into a target type. // This is done by means of calling _forceBridgeFromObjectiveC or // _conditionallyBridgeFromObjectiveC_bridgeable from the Target type. auto *funcRef = Builder.createFunctionRefFor(Loc, bridgingFunc); SubstitutionMap subMap = lookupBridgeToObjCProtocolSubs(target); auto MetaTy = MetatypeType::get(target, MetatypeRepresentation::Thick); auto SILMetaTy = F->getTypeLowering(MetaTy).getLoweredType(); auto *MetaTyVal = Builder.createMetatype(Loc, SILMetaTy); // Temporary to hold the intermediate result. AllocStackInst *Tmp = nullptr; CanType OptionalTy; SILValue outOptionalParam; if (isConditional) { // Create a temporary OptionalTy = OptionalType::get(Dest->getType().getASTType()) ->getImplementationType() ->getCanonicalType(); Tmp = Builder.createAllocStack(Loc, F->getLoweredType(OptionalTy)); outOptionalParam = Tmp; } else { outOptionalParam = Dest; } // Emit a retain. SILValue srcArg = Builder.emitCopyValueOperation(Loc, srcOp); SmallVector Args; Args.push_back(outOptionalParam); Args.push_back(srcArg); Args.push_back(MetaTyVal); auto *AI = Builder.createApply(Loc, funcRef, subMap, Args); // If we have guaranteed normal arguments, insert the destroy. // // TODO: Is it safe to just eliminate the initial retain? Builder.emitDestroyOperation(Loc, srcArg); // If we have an unconditional_checked_cast_addr, return early. We do not need // to handle any conditional code. if (isa(Inst)) { // Destroy the source value as unconditional_checked_cast_addr would. Builder.emitDestroyOperation(Loc, srcOp); eraseInstAction(Inst); return (newI) ? newI : AI; } auto *CCABI = cast(Inst); switch (CCABI->getConsumptionKind()) { case CastConsumptionKind::TakeAlways: Builder.emitDestroyOperation(Loc, srcOp); break; case CastConsumptionKind::TakeOnSuccess: { { // Insert a release in the success BB. SILBuilderWithScope successBuilder(SuccessBB->begin()); successBuilder.emitDestroyOperation(Loc, srcOp); } { // And a store in the failure BB. if (Builder.hasOwnership()) { SILBuilderWithScope failureBuilder(FailureBB->begin()); SILValue writeback = srcOp; SILType srcType = src->getType().getObjectType(); if (writeback->getType() != srcType) { writeback = failureBuilder.createUncheckedRefCast(Loc, writeback, srcType); } failureBuilder.emitStoreValueOperation(Loc, writeback, src, StoreOwnershipQualifier::Init); } } break; } case CastConsumptionKind::BorrowAlways: llvm_unreachable("checked_cast_addr_br never has BorrowAlways"); case CastConsumptionKind::CopyOnSuccess: // If we are performing copy_on_success, store the value back into memory // here since we loaded it. We may need to cast back to the actual // underlying type. if (Builder.hasOwnership()) { SILValue writeback = srcOp; SILType srcType = src->getType().getObjectType(); if (writeback->getType() != srcType) { writeback = Builder.createUncheckedRefCast(Loc, writeback, srcType); } Builder.emitStoreValueOperation(Loc, writeback, src, StoreOwnershipQualifier::Init); } break; } // Results should be checked in case we process a conditional // case. E.g. casts from NSArray into [SwiftType] may fail, i.e. return .None. if (isConditional) { // Copy the temporary into Dest. // Load from the optional. auto *SomeDecl = Builder.getASTContext().getOptionalSomeDecl(); auto *BridgeSuccessBB = Inst->getFunction()->createBasicBlockAfter(Builder.getInsertionBB()); SmallVector, 2> CaseBBs; CaseBBs.emplace_back(SomeDecl, BridgeSuccessBB); CaseBBs.emplace_back(mod.getASTContext().getOptionalNoneDecl(), FailureBB); Builder.createSwitchEnumAddr(Loc, outOptionalParam, nullptr, CaseBBs); Builder.setInsertionPoint(FailureBB->begin()); Builder.createDeallocStack(Loc, Tmp); Builder.setInsertionPoint(BridgeSuccessBB); auto Addr = Builder.createUncheckedTakeEnumDataAddr(Loc, outOptionalParam, SomeDecl); Builder.createCopyAddr(Loc, Addr, Dest, IsTake, IsInitialization); Builder.createDeallocStack(Loc, Tmp); SmallVector SuccessBBArgs; Builder.createBranch(Loc, SuccessBB, SuccessBBArgs); } eraseInstAction(Inst); return (newI) ? newI : AI; } //===----------------------------------------------------------------------===// // Swift -> ObjC Bridging Cast Optimization //===----------------------------------------------------------------------===// static bool canOptimizeCast(const swift::Type &BridgedTargetTy, swift::SILFunctionConventions &substConv, const SILFunction *F) { auto context = F->getTypeExpansionContext(); // DestTy is the type which we want to convert to SILType DestTy = F->getLoweredType(BridgedTargetTy->getCanonicalType()); // ConvTy is the return type of the _bridgeToObjectiveCImpl() auto ConvTy = substConv.getSILResultType(context).getObjectType(); if (ConvTy == DestTy) { // Destination is the same type return true; } // Check if a superclass/subclass of the source operand if (DestTy.isExactSuperclassOf(ConvTy)) { return true; } if (ConvTy.isExactSuperclassOf(DestTy)) { return true; } // check if it is a bridgeable CF type if (ConvTy.getASTType() == getNSBridgedClassOfCFClass(DestTy.getASTType())) { return true; } if (DestTy.getASTType() == getNSBridgedClassOfCFClass(ConvTy.getASTType())) { return true; } // All else failed - can't optimize this case return false; } static std::optional> findBridgeToObjCFunc(SILOptFunctionBuilder &functionBuilder, SILDynamicCastInst dynamicCast) { CanType sourceFormalType = dynamicCast.getSourceFormalType(); auto *mod = dynamicCast.getModule().getSwiftModule(); auto &ctx = mod->getASTContext(); auto loc = dynamicCast.getLocation(); auto bridgedProto = ctx.getProtocol( KnownProtocolKind::ObjectiveCBridgeable); auto conf = lookupConformance(sourceFormalType, bridgedProto); assert(conf && "_ObjectiveCBridgeable conformance should exist"); (void)conf; // Generate code to invoke _bridgeToObjectiveC ModuleDecl *modDecl = ctx.getLoadedModule(ctx.Id_Foundation); if (!modDecl) return std::nullopt; SmallVector results; modDecl->lookupMember(results, sourceFormalType.getNominalOrBoundGenericNominal(), ctx.Id_bridgeToObjectiveC, Identifier()); ArrayRef resultsRef(results); if (resultsRef.empty()) { mod->lookupMember( results, sourceFormalType.getNominalOrBoundGenericNominal(), ctx.Id_bridgeToObjectiveC, Identifier()); resultsRef = results; } if (resultsRef.size() != 1) return std::nullopt; auto *resultDecl = results.front(); auto memberDeclRef = SILDeclRef(resultDecl); auto *bridgedFunc = functionBuilder.getOrCreateFunction( loc, memberDeclRef, ForDefinition_t::NotForDefinition); // Get substitutions, if source is a bound generic type. auto subMap = sourceFormalType->getContextSubstitutionMap( resultDecl->getDeclContext()); // Implementation of _bridgeToObjectiveC could not be found. if (!bridgedFunc) return std::nullopt; // The bridging function must have the correct parent module set. This ensures // that IRGen sets correct linkage when this function comes from the same // module as being compiled. if (!bridgedFunc->getDeclContext()) bridgedFunc->setParentModule( resultDecl->getDeclContext()->getParentModule()); if (dynamicCast.getFunction()->isAnySerialized() && !bridgedFunc->hasValidLinkageForFragileRef(dynamicCast.getFunction()->getSerializedKind())) return std::nullopt; if (bridgedFunc->getLoweredFunctionType() ->getSingleResult() .isFormalIndirect()) return std::nullopt; return std::make_pair(bridgedFunc, subMap); } static SILValue computeFinalCastedValue(SILBuilderWithScope &builder, SILDynamicCastInst dynamicCast, ApplyInst *newAI) { auto loc = dynamicCast.getLocation(); auto convTy = newAI->getType(); bool isConditional = dynamicCast.isConditional(); auto sourceFormalTy = dynamicCast.getSourceFormalType(); auto destLoweredTy = dynamicCast.getTargetLoweredType().getObjectType(); auto destFormalTy = dynamicCast.getTargetFormalType(); assert(destLoweredTy == dynamicCast.getLoweredBridgedTargetObjectType() && "Expected Dest Type to be the same as BridgedTargetTy"); if (convTy == destLoweredTy) { return newAI; } if (destLoweredTy.isExactSuperclassOf(convTy)) { return builder.createUpcast(loc, newAI, destLoweredTy); } if (convTy.isExactSuperclassOf(destLoweredTy)) { // If we are not conditional, we are ok with the downcast via checked cast // fails since we will trap. if (!isConditional) { return builder.createUnconditionalCheckedCast( loc, dynamicCast.getCheckedCastOptions(), newAI, destLoweredTy, destFormalTy); } // Otherwise if we /are/ emitting a conditional cast, make sure that we // handle the failure gracefully. // // Since we are being returned the value at +1, we need to destroy the // newAI on failure. auto *failureBB = dynamicCast.getFailureBlock(); { SILBuilderWithScope innerBuilder(&*failureBB->begin(), builder); auto valueToDestroy = ([&]() -> SILValue { if (!innerBuilder.hasOwnership()) return newAI; return failureBB->createPhiArgument(newAI->getType(), OwnershipKind::Owned); }()); innerBuilder.emitDestroyOperation(loc, valueToDestroy); } auto *condBrSuccessBB = newAI->getFunction()->createBasicBlockAfter(newAI->getParent()); condBrSuccessBB->createPhiArgument(destLoweredTy, OwnershipKind::Owned); builder.createCheckedCastBranch(loc, /* isExact*/ false, dynamicCast.getCheckedCastOptions(), newAI, sourceFormalTy, destLoweredTy, destFormalTy, condBrSuccessBB, failureBB); builder.setInsertionPoint(condBrSuccessBB, condBrSuccessBB->begin()); return condBrSuccessBB->getArgument(0); } if (convTy.getASTType() == getNSBridgedClassOfCFClass(destLoweredTy.getASTType()) || destLoweredTy.getASTType() == getNSBridgedClassOfCFClass(convTy.getASTType())) { // Handle NS <-> CF toll-free bridging here. return SILValue(builder.createUncheckedRefCast(loc, newAI, destLoweredTy)); } llvm_unreachable( "optimizeBridgedSwiftToObjCCast: should never reach this condition: if " "the Destination does not have the same type, is not a bridgeable CF " "type and isn't a superclass/subclass of the source operand we should " "have bailed earlier."); } /// Create a call of _bridgeToObjectiveC which converts an _ObjectiveCBridgeable /// instance into a bridged ObjC type. SILInstruction * CastOptimizer::optimizeBridgedSwiftToObjCCast(SILDynamicCastInst dynamicCast) { SILInstruction *Inst = dynamicCast.getInstruction(); const SILFunction *F = Inst->getFunction(); CastConsumptionKind ConsumptionKind = dynamicCast.getBridgedConsumptionKind(); bool isConditional = dynamicCast.isConditional(); SILValue Src = dynamicCast.getSource(); SILValue Dest = dynamicCast.getDest(); CanType BridgedTargetTy = dynamicCast.getBridgedTargetType(); SILBasicBlock *SuccessBB = dynamicCast.getSuccessBlock(); SILBasicBlock *FailureBB = dynamicCast.getFailureBlock(); auto &M = Inst->getModule(); auto Loc = Inst->getLoc(); bool AddressOnlyType = false; if (!Src->getType().isLoadable(*F) || !Dest->getType().isLoadable(*F)) { AddressOnlyType = true; } // Find the _BridgedToObjectiveC protocol. SILFunction *bridgedFunc = nullptr; SubstitutionMap subMap; { auto result = findBridgeToObjCFunc(functionBuilder, dynamicCast); if (!result) return nullptr; std::tie(bridgedFunc, subMap) = result.value(); } SILType SubstFnTy = bridgedFunc->getLoweredType().substGenericArgs( M, subMap, TypeExpansionContext(*F)); SILFunctionConventions substConv(SubstFnTy.castTo(), M); // Check that this is a case that the authors of this code thought it could // handle. if (!canOptimizeCast(BridgedTargetTy, substConv, F)) { return nullptr; } SILBuilderWithScope Builder(Inst, builderContext); auto FnRef = Builder.createFunctionRefFor(Loc, bridgedFunc); auto ParamTypes = SubstFnTy.castTo()->getParameters(); SILValue oldSrc; if (Src->getType().isAddress() && !substConv.isSILIndirect(ParamTypes[0])) { // Create load oldSrc = Src; Src = Builder.emitLoadValueOperation(Loc, Src, LoadOwnershipQualifier::Take); } // Compensate different owning conventions of the replaced cast instruction // and the inserted conversion function. bool needReleaseAfterCall = false; bool needReleaseInSuccess = false; switch (ParamTypes[0].getConvention()) { case ParameterConvention::Direct_Guaranteed: case ParameterConvention::Indirect_In_Guaranteed: switch (ConsumptionKind) { case CastConsumptionKind::TakeAlways: needReleaseAfterCall = true; break; case CastConsumptionKind::TakeOnSuccess: needReleaseInSuccess = true; break; case CastConsumptionKind::BorrowAlways: llvm_unreachable("Should never hit this"); case CastConsumptionKind::CopyOnSuccess: // We assume that our caller is correct and will treat our argument as // being immutable, so we do not need to do anything here. break; } break; case ParameterConvention::Pack_Guaranteed: case ParameterConvention::Pack_Owned: case ParameterConvention::Pack_Inout: case ParameterConvention::Direct_Owned: case ParameterConvention::Indirect_In: case ParameterConvention::Indirect_In_CXX: // Currently this // cannot appear, because the _bridgeToObjectiveC protocol witness method // always receives the this pointer (= the source) as guaranteed. // If it became possible (perhaps with the advent of ownership and // explicit +1 annotations), the implementation should look something // like this: /* switch (ConsumptionKind) { case CastConsumptionKind::TakeAlways: break; case CastConsumptionKind::TakeOnSuccess: needRetainBeforeCall = true; needReleaseInSuccess = true; break; case CastConsumptionKind::CopyOnSuccess: needRetainBeforeCall = true; break; } break; */ llvm_unreachable("this should never happen so is currently untestable"); case ParameterConvention::Direct_Unowned: assert(!AddressOnlyType && "AddressOnlyType with Direct_Unowned is not supported"); break; case ParameterConvention::Indirect_Inout: case ParameterConvention::Indirect_InoutAliasable: // TODO handle remaining indirect argument types return nullptr; } // Generate a code to invoke the bridging function. auto *NewAI = Builder.createApply(Loc, FnRef, subMap, Src); // First if we are going to destroy the value unconditionally, just insert the // destroy right after the call. This handles some of the conditional cases // and /all/ of the consuming unconditional cases. if (needReleaseAfterCall) { Builder.emitDestroyOperation(Loc, Src); } else { if (SuccessBB) { SILBuilderWithScope succBuilder(&*SuccessBB->begin(), Builder); if (needReleaseInSuccess) { succBuilder.emitDestroyOperation(Loc, Src); } else { if (oldSrc) { succBuilder.emitStoreValueOperation(Loc, Src, oldSrc, StoreOwnershipQualifier::Init); } } SILBuilderWithScope failBuilder(&*FailureBB->begin(), Builder); if (oldSrc) { failBuilder.emitStoreValueOperation(Loc, Src, oldSrc, StoreOwnershipQualifier::Init); } } else { if (oldSrc) { Builder.emitStoreValueOperation(Loc, Src, oldSrc, StoreOwnershipQualifier::Init); } } } if (!Dest) return NewAI; // If it is addr cast then store the result into the dest. // // NOTE: We assume that dest was uninitialized when passed to us. SILValue castedValue = computeFinalCastedValue(Builder, dynamicCast, NewAI); auto qual = Builder.hasOwnership() ? StoreOwnershipQualifier::Init : StoreOwnershipQualifier::Unqualified; SILInstruction *NewI = Builder.createStore(Loc, castedValue, Dest, qual); if (isConditional && NewI->getParent() != NewAI->getParent()) { Builder.createBranch(Loc, SuccessBB); } eraseInstAction(Inst); return NewI; } //===----------------------------------------------------------------------===// // Top Level Bridge Cast Optimization Entrypoint //===----------------------------------------------------------------------===// /// Make use of the fact that some of these casts cannot fail. For /// example, if the ObjC type is exactly the expected _ObjectiveCType /// type, then it would always succeed for NSString, NSNumber, etc. /// Casts from NSArray, NSDictionary and NSSet may fail. /// /// If ObjC class is not exactly _ObjectiveCType, then its conversion /// to a required _ObjectiveCType may fail. SILInstruction * CastOptimizer::optimizeBridgedCasts(SILDynamicCastInst dynamicCast) { CanType source = dynamicCast.getSourceFormalType(); CanType target = dynamicCast.getTargetFormalType(); auto &M = dynamicCast.getModule(); // To apply the bridged optimizations, we should ensure that types are not // existential (and keep in mind that generic parameters can be existentials), // and that one of the types is a class and another one is a struct. if (source.isAnyExistentialType() || target.isAnyExistentialType() || source->is() || target->is() || (source.getClassOrBoundGenericClass() && !target.getStructOrBoundGenericStruct()) || (target.getClassOrBoundGenericClass() && !source.getStructOrBoundGenericStruct())) return nullptr; // Casts involving non-bound generic types cannot be optimized. if (source->hasArchetype() || target->hasArchetype()) return nullptr; CanType CanBridgedSourceTy = dynamicCast.getBridgedSourceType(); CanType CanBridgedTargetTy = dynamicCast.getBridgedTargetType(); // If we were unable to bridge either of our source/target types, return // nullptr. if (!CanBridgedSourceTy || !CanBridgedTargetTy) return nullptr; if (CanBridgedSourceTy == source && CanBridgedTargetTy == target) { // Both source and target type are ObjC types. return nullptr; } if (CanBridgedSourceTy != source && CanBridgedTargetTy != target) { // Both source and target type are Swift types. return nullptr; } if ((CanBridgedSourceTy && CanBridgedSourceTy->getAnyNominal() == M.getASTContext().getNSErrorDecl()) || (CanBridgedTargetTy && CanBridgedTargetTy->getAnyNominal() == M.getASTContext().getNSErrorDecl())) { // FIXME: Can't optimize bridging with NSError. return nullptr; } // Check what kind of conversion it is? ObjC->Swift or Swift-ObjC? if (CanBridgedTargetTy != target) { // This is an ObjC to Swift cast. return optimizeBridgedObjCToSwiftCast(dynamicCast); } else { // This is a Swift to ObjC cast return optimizeBridgedSwiftToObjCCast(dynamicCast); } llvm_unreachable("Unknown kind of bridging"); } //===----------------------------------------------------------------------===// // Cast Optimizer Public API //===----------------------------------------------------------------------===// SILInstruction *CastOptimizer::simplifyCheckedCastAddrBranchInst( CheckedCastAddrBranchInst *Inst) { if (auto *I = optimizeCheckedCastAddrBranchInst(Inst)) Inst = dyn_cast(I); if (!Inst) return nullptr; SILDynamicCastInst dynamicCast(Inst); auto Loc = dynamicCast.getLocation(); auto Src = dynamicCast.getSource(); auto Dest = dynamicCast.getDest(); auto *SuccessBB = dynamicCast.getSuccessBlock(); auto *FailureBB = dynamicCast.getFailureBlock(); SILBuilderWithScope Builder(Inst, builderContext); // Check if we can statically predict the outcome of the cast. auto Feasibility = dynamicCast.classifyFeasibility(true /*allow whole module*/); if (Feasibility == DynamicCastFeasibility::WillFail) { if (shouldDestroyOnFailure(Inst->getConsumptionKind())) { auto &srcTL = Builder.getTypeLowering(Src->getType()); srcTL.emitDestroyAddress(Builder, Loc, Src); } auto NewI = Builder.createBranch(Loc, FailureBB); eraseInstAction(Inst); willFailAction(); return NewI; } bool ResultNotUsed = isa(Dest); if (ResultNotUsed) { for (auto Use : Dest->getUses()) { auto *User = Use->getUser(); if (isa(User) || isa(User) || User == Inst) continue; ResultNotUsed = false; break; } } auto *BB = Inst->getParent(); SILInstruction *BridgedI = nullptr; // To apply the bridged optimizations, we should // ensure that types are not existential, // and that not both types are classes. BridgedI = optimizeBridgedCasts(dynamicCast); if (!BridgedI) { // If the cast may succeed or fail, and it can't be optimized into a // bridging operation, then let it be. if (Feasibility == DynamicCastFeasibility::MaySucceed) { return nullptr; } assert(Feasibility == DynamicCastFeasibility::WillSucceed); // Replace by unconditional_addr_cast, followed by a branch. // The unconditional_addr_cast can be skipped, if the result of a cast // is not used afterwards. if (ResultNotUsed) { if (shouldTakeOnSuccess(Inst->getConsumptionKind())) { auto &srcTL = Builder.getTypeLowering(Src->getType()); srcTL.emitDestroyAddress(Builder, Loc, Src); } for (auto iter = Dest->use_begin(); iter != Dest->use_end();) { SILInstruction *user = (*iter++)->getUser(); if (isa(user)) eraseInstAction(user); } eraseInstAction(Inst); Builder.setInsertionPoint(BB); auto *NewI = Builder.createBranch(Loc, SuccessBB); willSucceedAction(); return NewI; } // Since it is an addr cast, only address types are handled here. if (!Src->getType().isAddress() || !Dest->getType().isAddress()) { return nullptr; } // Both TakeOnSuccess and TakeAlways can be reduced to an // UnconditionalCheckedCast, since the failure path is irrelevant. AllocStackInst *copiedSrc = nullptr; switch (Inst->getConsumptionKind()) { case CastConsumptionKind::BorrowAlways: llvm_unreachable("checked_cast_addr_br never has BorrowAlways"); case CastConsumptionKind::CopyOnSuccess: if (!Src->getType().isTrivial(*BB->getParent())) { copiedSrc = Builder.createAllocStack(Loc, Src->getType()); Builder.createCopyAddr(Loc, Src, copiedSrc, IsNotTake, IsInitialization); Src = copiedSrc; } break; case CastConsumptionKind::TakeAlways: case CastConsumptionKind::TakeOnSuccess: break; } bool result = emitSuccessfulIndirectUnconditionalCast( Builder, Builder.getModule().getSwiftModule(), Loc, Src, Inst->getSourceFormalType(), Dest, Inst->getTargetFormalType(), Inst); (void)result; assert(result && "emit cannot fail for an checked_cast_addr_br"); if (copiedSrc) Builder.createDeallocStack(Loc, copiedSrc); eraseInstAction(Inst); } SILInstruction *NewI = &BB->back(); if (!isa(NewI)) { Builder.setInsertionPoint(BB); NewI = Builder.createBranch(Loc, SuccessBB); } willSucceedAction(); return NewI; } SILInstruction * CastOptimizer::simplifyCheckedCastBranchInst(CheckedCastBranchInst *Inst) { if (Inst->isExact()) { SILDynamicCastInst dynamicCast(Inst); auto *ARI = dyn_cast(stripUpCasts(dynamicCast.getSource())); if (!ARI) return nullptr; // We know the dynamic type of the operand. SILBuilderWithScope Builder(Inst, builderContext); auto Loc = dynamicCast.getLocation(); if (ARI->getType() == dynamicCast.getTargetLoweredType()) { // This exact cast will succeed. SmallVector Args; Args.push_back(ARI); auto *NewI = Builder.createBranch(Loc, dynamicCast.getSuccessBlock(), Args); eraseInstAction(Inst); willSucceedAction(); return NewI; } // This exact cast will fail. With ownership enabled, we pass a copy of the // original casts value to the failure block. TinyPtrVector Args; if (Builder.hasOwnership()) Args.push_back(dynamicCast.getSource()); auto *NewI = Builder.createBranch(Loc, dynamicCast.getFailureBlock(), Args); eraseInstAction(Inst); willFailAction(); return NewI; } if (auto *I = optimizeCheckedCastBranchInst(Inst)) Inst = dyn_cast(I); if (!Inst) return nullptr; SILDynamicCastInst dynamicCast(Inst); auto TargetLoweredType = dynamicCast.getTargetLoweredType(); auto TargetFormalType = dynamicCast.getTargetFormalType(); auto Loc = dynamicCast.getLocation(); auto *SuccessBB = dynamicCast.getSuccessBlock(); auto *FailureBB = dynamicCast.getFailureBlock(); auto Op = dynamicCast.getSource(); auto *F = dynamicCast.getFunction(); // Check if we can statically predict the outcome of the cast. auto Feasibility = dynamicCast.classifyFeasibility(false /*allow whole module*/); if (Feasibility == DynamicCastFeasibility::MaySucceed) { return nullptr; } SILBuilderWithScope Builder(Inst, builderContext); if (Feasibility == DynamicCastFeasibility::WillFail) { auto *NewI = Builder.createBranch(Loc, FailureBB); if (Builder.hasOwnership()) { FailureBB->getArgument(0)->replaceAllUsesWith(Op); FailureBB->eraseArgument(0); SuccessBB->getArgument(0)->replaceAllUsesWithUndef(); SuccessBB->eraseArgument(0); } eraseInstAction(Inst); willFailAction(); return NewI; } assert(Feasibility == DynamicCastFeasibility::WillSucceed); bool ResultNotUsed = SuccessBB->getArgument(0)->use_empty(); SILValue CastedValue; if (Op->getType() != TargetLoweredType) { // Apply the bridged cast optimizations. // // TODO: Bridged casts cannot be expressed by checked_cast_br yet. // Should we ever support it, please review this code. auto BridgedI = optimizeBridgedCasts(dynamicCast); if (BridgedI) { llvm_unreachable( "Bridged casts cannot be expressed by checked_cast_br yet"); } else { // Replace by unconditional_cast, followed by a branch. // The unconditional_cast can be skipped, if the result of a cast // is not used afterwards. if (!ResultNotUsed) { ASSERT(dynamicCast.canSILUseScalarCheckedCastInstructions()); CastedValue = emitSuccessfulScalarUnconditionalCast(Builder, Loc, dynamicCast); } else { CastedValue = SILUndef::get(F, TargetLoweredType); } if (!CastedValue) CastedValue = Builder.createUnconditionalCheckedCast( Loc, Inst->getCheckedCastOptions(), Op, TargetLoweredType, TargetFormalType); } } else { // No need to cast. CastedValue = Op; } BranchInst *NewI = nullptr; if (Builder.hasOwnership()) { NewI = Builder.createBranch(Loc, SuccessBB); SuccessBB->getArgument(0)->replaceAllUsesWith(CastedValue); SuccessBB->eraseArgument(0); FailureBB->getArgument(0)->replaceAllUsesWithUndef(); FailureBB->eraseArgument(0); } else { NewI = Builder.createBranch(Loc, SuccessBB, CastedValue); } eraseInstAction(Inst); willSucceedAction(); return NewI; } SILInstruction *CastOptimizer::optimizeCheckedCastAddrBranchInst( CheckedCastAddrBranchInst *Inst) { auto Loc = Inst->getLoc(); auto Src = Inst->getSrc(); auto Dest = Inst->getDest(); auto *SuccessBB = Inst->getSuccessBB(); auto *FailureBB = Inst->getFailureBB(); // If there is an unbound generic type involved in the cast, bail. if (Src->getType().hasArchetype() || Dest->getType().hasArchetype()) return nullptr; // %1 = metatype $A.Type // [%2 = init_existential_metatype %1 ...] // %3 = alloc_stack // store %1 to %3 or store %2 to %3 // checked_cast_addr_br %3 to ... // -> // %1 = metatype $A.Type // %c = checked_cast_br %1 to ... // store %c to %3 (if successful) if (auto *ASI = dyn_cast(Src)) { // Check if the value of this alloc_stack is set only once by a store // instruction, used only by CCABI and then deallocated. bool isLegal = true; StoreInst *Store = nullptr; for (auto Use : ASI->getUses()) { auto *User = Use->getUser(); if (isa(User) || User == Inst) continue; if (auto *SI = dyn_cast(User)) { if (SI->getOwnershipQualifier() == StoreOwnershipQualifier::Assign) { // We do not handle [assign] isLegal = false; break; } if (!Store) { Store = SI; continue; } } isLegal = false; break; } if (isLegal && Store) { // Check what was the value stored in the allocated stack slot. auto Src = Store->getSrc(); MetatypeInst *MI = nullptr; if (auto *IEMI = dyn_cast(Src)) { MI = dyn_cast(IEMI->getOperand()); } if (!MI) MI = dyn_cast(Src); if (MI) { if (SuccessBB->getSinglePredecessorBlock() && canOptimizeToScalarCheckedCastInstructions( Inst->getFunction(), MI->getType().getASTType(), Inst->getTargetFormalType(), Inst->getConsumptionKind())) { SILBuilderWithScope B(Inst, builderContext); auto NewI = B.createCheckedCastBranch( Loc, false /*isExact*/, Inst->getCheckedCastOptions(), MI, Inst->getSourceFormalType(), Inst->getTargetLoweredType().getObjectType(), Inst->getTargetFormalType(), SuccessBB, FailureBB, Inst->getTrueBBCount(), Inst->getFalseBBCount()); SuccessBB->createPhiArgument(Dest->getType().getObjectType(), OwnershipKind::Owned); B.setInsertionPoint(SuccessBB->begin()); // Store the result B.emitStoreValueOperation(Loc, SuccessBB->getArgument(0), Dest, StoreOwnershipQualifier::Trivial); if (B.hasOwnership()) FailureBB->createPhiArgument(MI->getType(), OwnershipKind::None); eraseInstAction(Inst); return NewI; } } } } return nullptr; } SILInstruction * CastOptimizer::optimizeCheckedCastBranchInst(CheckedCastBranchInst *Inst) { if (Inst->isExact()) return nullptr; // InstOptUtils.helper we use to simplify replacing a checked_cast_branch with // an optimized checked cast branch. auto replaceCastHelper = [](SILBuilderWithScope &B, SILDynamicCastInst dynamicCast, MetatypeInst *mi) -> SILInstruction * { // Make sure that the failure block has the new metatype type for // its default argument as required when we are in ossa // mode. Without ossa, failure blocks do not have args, so we do // not need to do anything. auto *fBlock = dynamicCast.getFailureBlock(); if (B.hasOwnership()) { fBlock->replacePhiArgumentAndReplaceAllUses(0, mi->getType(), OwnershipKind::None); } return B.createCheckedCastBranch( dynamicCast.getLocation(), false /*isExact*/, dynamicCast.getCheckedCastOptions(), mi, // The cast is now from the MetatypeInst, so get the source formal // type from it. mi->getType().getASTType(), dynamicCast.getTargetLoweredType(), dynamicCast.getTargetFormalType(), dynamicCast.getSuccessBlock(), fBlock, *dynamicCast.getSuccessBlockCount(), *dynamicCast.getFailureBlockCount()); }; SILDynamicCastInst dynamicCast(Inst); auto Op = dynamicCast.getSource(); // Try to simplify checked_cond_br instructions using existential // metatypes by propagating a concrete type whenever it can be // determined statically. // %0 = metatype $A.Type // %1 = init_existential_metatype ..., %0: $A // checked_cast_br %1, .... // -> // %0 = metatype $A.Type // checked_cast_br %0 to ... if (auto *IEMI = dyn_cast(Op)) { if (auto *MI = dyn_cast(IEMI->getOperand())) { SILBuilderWithScope B(Inst, builderContext); auto *NewI = replaceCastHelper(B, dynamicCast, MI); eraseInstAction(Inst); return NewI; } } if (auto *EMI = dyn_cast(Op)) { // Operand of the existential_metatype instruction. auto Op = EMI->getOperand(); auto EmiTy = EMI->getType(); // %0 = alloc_stack $T // %1 = init_existential_addr %0: $*T, $A // %2 = existential_metatype $T.Type, %0: $*T // checked_cast_br %2 to ... // -> // %1 = metatype $A.Type // checked_cast_br %1 to ... if (auto *ASI = dyn_cast(Op)) { // Should be in the same BB. if (ASI->getParent() != EMI->getParent()) return nullptr; // Check if this alloc_stack is only initialized once by means of // single init_existential_addr. bool isLegal = true; // init_existential instruction used to initialize this alloc_stack. InitExistentialAddrInst *FoundIEI = nullptr; for (auto Use : getNonDebugUses(ASI)) { auto *User = Use->getUser(); if (isa(User) || isa(User) || isa(User)) continue; if (auto *IEI = dyn_cast(User)) { if (!FoundIEI) { FoundIEI = IEI; continue; } } isLegal = false; break; } if (isLegal && FoundIEI) { // Should be in the same BB. if (FoundIEI->getParent() != EMI->getParent()) return nullptr; // Get the type used to initialize the existential. auto LoweredConcreteTy = FoundIEI->getLoweredConcreteType(); // We don't know enough at compile time about existential // and generic type parameters. if (LoweredConcreteTy.isAnyExistentialType() || LoweredConcreteTy.is()) return nullptr; // Get the metatype of this type. auto EMT = EmiTy.castTo(); auto *MetaTy = MetatypeType::get(LoweredConcreteTy.getASTType(), EMT->getRepresentation()); auto CanMetaTy = CanTypeWrapper(MetaTy); auto SILMetaTy = SILType::getPrimitiveObjectType(CanMetaTy); SILBuilderWithScope B(Inst, builderContext); auto *MI = B.createMetatype(FoundIEI->getLoc(), SILMetaTy); auto *NewI = replaceCastHelper(B, dynamicCast, MI); eraseInstAction(Inst); return NewI; } } // %0 = alloc_ref $A // %1 = init_existential_ref %0: $A, $... // %2 = existential_metatype ..., %1 : ... // checked_cast_br %2, .... // -> // %1 = metatype $A.Type // checked_cast_br %1, .... if (auto *FoundIERI = dyn_cast(Op)) { SILValue op = FoundIERI->getOperand(); if (auto *eir = dyn_cast(op)) op = eir->getOperand(); if (!isa(op)) return nullptr; // Get the type used to initialize the existential. auto ConcreteTy = FoundIERI->getFormalConcreteType(); // We don't know enough at compile time about existential // and generic type parameters. if (ConcreteTy.isAnyExistentialType() || ConcreteTy->is()) return nullptr; // Get the SIL metatype of this type. auto EMT = EMI->getType().castTo(); auto *MetaTy = MetatypeType::get(ConcreteTy, EMT->getRepresentation()); auto CanMetaTy = CanTypeWrapper(MetaTy); auto SILMetaTy = SILType::getPrimitiveObjectType(CanMetaTy); SILBuilderWithScope B(Inst, builderContext); auto *MI = B.createMetatype(FoundIERI->getLoc(), SILMetaTy); auto *NewI = replaceCastHelper(B, dynamicCast, MI); eraseInstAction(Inst); return NewI; } } return nullptr; } ValueBase *CastOptimizer::optimizeUnconditionalCheckedCastInst( UnconditionalCheckedCastInst *Inst) { SILDynamicCastInst dynamicCast(Inst); auto Loc = dynamicCast.getLocation(); // Check if we can statically predict the outcome of the cast. auto Feasibility = dynamicCast.classifyFeasibility(false /*allowWholeModule*/); if (Feasibility == DynamicCastFeasibility::WillFail) { // Remove the cast and insert a trap, followed by an // unreachable instruction. SILBuilderWithScope Builder(Inst, builderContext); auto *Trap = Builder.createUnconditionalFail(Loc, "failed cast"); Inst->replaceAllUsesWithUndef(); eraseInstAction(Inst); Builder.setInsertionPoint(std::next(SILBasicBlock::iterator(Trap))); auto *UnreachableInst = Builder.createUnreachable(ArtificialUnreachableLocation()); // Delete everything after the unreachable except for dealloc_stack which we // move before the trap. deleteInstructionsAfterUnreachable(UnreachableInst, Trap); willFailAction(); return nullptr; } if (Feasibility == DynamicCastFeasibility::WillSucceed) { if (Inst->use_empty()) { eraseInstAction(Inst); willSucceedAction(); return nullptr; } } SILBuilderWithScope Builder(Inst, builderContext); // Try to apply the bridged casts optimizations auto NewI = optimizeBridgedCasts(dynamicCast); if (NewI) { // FIXME: I'm not sure why this is true! auto newValue = cast(NewI); replaceInstUsesAction(Inst, newValue); eraseInstAction(Inst); willSucceedAction(); return newValue; } // If the cast may succeed or fail and can't be optimized into a bridging // call, let it be. if (Feasibility == DynamicCastFeasibility::MaySucceed) { return nullptr; } assert(Feasibility == DynamicCastFeasibility::WillSucceed); if (dynamicCast.isBridgingCast()) return nullptr; auto Result = emitSuccessfulScalarUnconditionalCast(Builder, Loc, dynamicCast); if (!Result) { // No optimization was possible. return nullptr; } replaceInstUsesAction(Inst, Result); eraseInstAction(Inst); willSucceedAction(); return Result; } /// Deletes all instructions after \p UnreachableInst except dealloc_stack /// instructions are moved before \p TrapInst. void CastOptimizer::deleteInstructionsAfterUnreachable( SILInstruction *UnreachableInst, SILInstruction *TrapInst) { auto UnreachableInstIt = std::next(SILBasicBlock::iterator(UnreachableInst)); auto *Block = TrapInst->getParent(); while (UnreachableInstIt != Block->end()) { SILInstruction *CurInst = &*UnreachableInstIt; ++UnreachableInstIt; CurInst->replaceAllUsesOfAllResultsWithUndef(); eraseInstAction(CurInst); } } /// TODO: Move to emitSuccessfulIndirectUnconditionalCast? /// /// Peephole to avoid runtime calls: /// unconditional_checked_cast_addr T in %0 : $*T to P in %1 : $*P /// -> /// %addr = init_existential_addr %1 : $*P, T /// copy_addr %0 to %addr /// /// where T is a type statically known to conform to P. /// /// In caase P is a class existential type, it generates: /// %val = load %0 : $*T /// %existential = init_existential_ref %val : $T, $T, P /// store %existential to %1 : $*P /// /// Returns true if the optimization was possible and false otherwise. static bool optimizeStaticallyKnownProtocolConformance( UnconditionalCheckedCastAddrInst *Inst) { auto Loc = Inst->getLoc(); auto Src = Inst->getSrc(); auto Dest = Inst->getDest(); auto SourceType = Inst->getSourceFormalType(); auto TargetType = Inst->getTargetFormalType(); auto &Mod = Inst->getModule(); if (TargetType->isAnyExistentialType() && !SourceType->canBeExistential()) { auto &Ctx = Mod.getASTContext(); auto *Proto = dyn_cast_or_null(TargetType->getAnyNominal()); if (!Proto) return false; // SourceType is a non-existential type with a non-conditional // conformance to a protocol represented by the TargetType. // // swift::checkConformance() checks any conditional conformances. If // they depend on information not known until runtime, the conformance // will not be returned. For instance, if `X: P` where `T == Int` in `func // foo(_: T) { ... X() as? P ... }`, the cast will succeed for // `foo(0)` but not for `foo("string")`. There are many cases where // everything is completely static (`X() as? P`), in which case a // valid conformance will be returned. auto Conformance = checkConformance(SourceType, Proto); if (Conformance.isInvalid()) return false; if (!matchesActorIsolation(Conformance, Inst->getFunction())) return false; auto layout = TargetType->getExistentialLayout(); if (layout.getProtocols().size() != 1) return false; SILBuilderWithScope B(Inst); SmallVector NewConformances; NewConformances.push_back(Conformance); ArrayRef Conformances = Ctx.AllocateCopy(NewConformances); auto ExistentialRepr = Dest->getType().getPreferredExistentialRepresentation(SourceType); switch (ExistentialRepr) { default: return false; case ExistentialRepresentation::Opaque: { auto ExistentialAddr = B.createInitExistentialAddr( Loc, Dest, SourceType, Src->getType().getObjectType(), Conformances); B.createCopyAddr(Loc, Src, ExistentialAddr, IsTake_t::IsTake, IsInitialization_t::IsInitialization); break; } case ExistentialRepresentation::Class: { auto Value = B.emitLoadValueOperation(Loc, Src, LoadOwnershipQualifier::Take); auto Existential = B.createInitExistentialRef(Loc, Dest->getType().getObjectType(), SourceType, Value, Conformances); B.emitStoreValueOperation(Loc, Existential, Dest, StoreOwnershipQualifier::Init); break; } case ExistentialRepresentation::Boxed: { auto AllocBox = B.createAllocExistentialBox(Loc, Dest->getType(), SourceType, Conformances); auto Projection = B.createProjectExistentialBox(Loc, Src->getType(), AllocBox); // This needs to be a copy_addr (for now) because we must handle // address-only types. B.createCopyAddr(Loc, Src, Projection, IsTake, IsInitialization); B.emitStoreValueOperation(Loc, AllocBox, Dest, StoreOwnershipQualifier::Init); break; } }; return true; } // Not a concrete -> existential cast. return false; } SILInstruction *CastOptimizer::optimizeUnconditionalCheckedCastAddrInst( UnconditionalCheckedCastAddrInst *Inst) { SILDynamicCastInst dynamicCast(Inst); auto Loc = dynamicCast.getLocation(); // Check if we can statically predict the outcome of the cast. auto Feasibility = dynamicCast.classifyFeasibility(false /*allow whole module*/); if (Feasibility == DynamicCastFeasibility::MaySucceed) { // Forced bridged casts can be still simplified here. // If they fail, they fail inside the conversion function. if (!dynamicCast.isBridgingCast()) return nullptr; } if (Feasibility == DynamicCastFeasibility::WillFail) { // Remove the cast and insert a trap, followed by an // unreachable instruction. SILBuilderWithScope Builder(Inst, builderContext); auto *TrapI = Builder.createUnconditionalFail(Loc, "failed cast"); eraseInstAction(Inst); Builder.setInsertionPoint(std::next(TrapI->getIterator())); auto *UnreachableInst = Builder.createUnreachable(ArtificialUnreachableLocation()); // Delete everything after the unreachable except for dealloc_stack which we // move before the trap. deleteInstructionsAfterUnreachable(UnreachableInst, TrapI); willFailAction(); } if (Feasibility == DynamicCastFeasibility::WillSucceed || Feasibility == DynamicCastFeasibility::MaySucceed) { // Check if a result of a cast is unused. If this is the case, the cast can // be removed even if the cast may fail at runtime. // Swift optimizer does not claim to be crash-preserving. SILValue dest = dynamicCast.getDest(); bool ResultNotUsed = isa(dest); DestroyAddrInst *DestroyDestInst = nullptr; if (ResultNotUsed) { for (auto Use : dest->getUses()) { auto *User = Use->getUser(); if (isa(User) || User == Inst) continue; if (isa(User) && !DestroyDestInst) { DestroyDestInst = cast(User); continue; } ResultNotUsed = false; DestroyDestInst = nullptr; break; } } if (ResultNotUsed) { SILBuilderWithScope B(Inst, builderContext); B.createDestroyAddr(Loc, dynamicCast.getSource()); if (DestroyDestInst) eraseInstAction(DestroyDestInst); eraseInstAction(Inst); willSucceedAction(); return nullptr; } // Try to apply the bridged casts optimizations. auto NewI = optimizeBridgedCasts(dynamicCast); if (NewI) { willSucceedAction(); return nullptr; } if (Feasibility == DynamicCastFeasibility::MaySucceed) return nullptr; assert(Feasibility == DynamicCastFeasibility::WillSucceed); if (optimizeStaticallyKnownProtocolConformance(Inst)) { eraseInstAction(Inst); willSucceedAction(); return nullptr; } if (dynamicCast.isBridgingCast()) return nullptr; SILBuilderWithScope Builder(Inst, builderContext); if (!emitSuccessfulIndirectUnconditionalCast(Builder, Loc, dynamicCast)) { // No optimization was possible. return nullptr; } eraseInstAction(Inst); willSucceedAction(); } return nullptr; } /// Simplify conversions between thick and objc metatypes. SILValue CastOptimizer::optimizeMetatypeConversion( ConversionOperation mci, MetatypeRepresentation representation) { SILValue op = mci->getOperand(0); // Instruction has a proper target type already. SILType ty = mci->getType(); auto metatypeTy = op->getType().getAs(); if (metatypeTy->getRepresentation() != representation) return SILValue(); auto loc = mci->getLoc(); // Rematerialize the incoming metatype instruction with the outgoing type. auto replaceCast = [&](SILValue newValue) -> SILValue { assert(ty.getAs()->getRepresentation() == newValue->getType().getAs()->getRepresentation()); replaceValueUsesAction(*mci, newValue); eraseInstAction(*mci); return newValue; }; if (isa(op)) { return replaceCast( SILBuilderWithScope(*mci, builderContext).createMetatype(loc, ty)); } // For metatype instructions that require an operand, generate the new // metatype at the same position as the original to avoid extending the // lifetime of `op` past its destroy. if (auto *vmi = dyn_cast(op)) { return replaceCast(SILBuilderWithScope(vmi, builderContext) .createValueMetatype(loc, ty, vmi->getOperand())); } if (auto *emi = dyn_cast(op)) { return replaceCast( SILBuilderWithScope(emi, builderContext) .createExistentialMetatype(loc, ty, emi->getOperand())); } return SILValue(); }