diff --git a/include/swift/SIL/SILBuilder.h b/include/swift/SIL/SILBuilder.h index b9d82b153901b..377c3cbdd46bd 100644 --- a/include/swift/SIL/SILBuilder.h +++ b/include/swift/SIL/SILBuilder.h @@ -797,9 +797,11 @@ class SILBuilder { } ConvertEscapeToNoEscapeInst * - createConvertEscapeToNoEscape(SILLocation Loc, SILValue Op, SILType Ty) { + createConvertEscapeToNoEscape(SILLocation Loc, SILValue Op, SILType Ty, + bool lifetimeGuaranteed) { return insert(ConvertEscapeToNoEscapeInst::create( - getSILDebugLocation(Loc), Op, Ty, getFunction(), OpenedArchetypes)); + getSILDebugLocation(Loc), Op, Ty, getFunction(), OpenedArchetypes, + lifetimeGuaranteed)); } ThinFunctionToPointerInst * diff --git a/include/swift/SIL/SILCloner.h b/include/swift/SIL/SILCloner.h index 239c114a742b6..3138d24804b98 100644 --- a/include/swift/SIL/SILCloner.h +++ b/include/swift/SIL/SILCloner.h @@ -984,10 +984,10 @@ template void SILCloner::visitConvertEscapeToNoEscapeInst( ConvertEscapeToNoEscapeInst *Inst) { getBuilder().setCurrentDebugScope(getOpScope(Inst->getDebugScope())); - doPostProcess(Inst, getBuilder().createConvertEscapeToNoEscape( - getOpLocation(Inst->getLoc()), - getOpValue(Inst->getOperand()), - getOpType(Inst->getType()))); + doPostProcess( + Inst, getBuilder().createConvertEscapeToNoEscape( + getOpLocation(Inst->getLoc()), getOpValue(Inst->getOperand()), + getOpType(Inst->getType()), Inst->isLifetimeGuaranteed())); } template diff --git a/include/swift/SIL/SILInstruction.h b/include/swift/SIL/SILInstruction.h index 29b5d649a87cf..de6b894707564 100644 --- a/include/swift/SIL/SILInstruction.h +++ b/include/swift/SIL/SILInstruction.h @@ -3916,18 +3916,26 @@ class ConvertEscapeToNoEscapeInst final ConvertEscapeToNoEscapeInst, ConversionInst> { friend SILBuilder; + bool lifetimeGuaranteed; + ConvertEscapeToNoEscapeInst(SILDebugLocation DebugLoc, SILValue Operand, - ArrayRef TypeDependentOperands, - SILType Ty) + ArrayRef TypeDependentOperands, + SILType Ty, bool isLifetimeGuaranteed) : UnaryInstructionWithTypeDependentOperandsBase( - DebugLoc, Operand, TypeDependentOperands, Ty) { + DebugLoc, Operand, TypeDependentOperands, Ty), + lifetimeGuaranteed(isLifetimeGuaranteed) { assert(!Operand->getType().castTo()->isNoEscape()); assert(Ty.castTo()->isNoEscape()); } static ConvertEscapeToNoEscapeInst * create(SILDebugLocation DebugLoc, SILValue Operand, SILType Ty, - SILFunction &F, SILOpenedArchetypesState &OpenedArchetypes); + SILFunction &F, SILOpenedArchetypesState &OpenedArchetypes, + bool lifetimeGuaranteed); +public: + bool isLifetimeGuaranteed() const { + return lifetimeGuaranteed; + } }; /// ThinFunctionToPointerInst - Convert a thin function pointer to a diff --git a/include/swift/Serialization/ModuleFormat.h b/include/swift/Serialization/ModuleFormat.h index aada1b9d3d9fe..89cc359f11bf3 100644 --- a/include/swift/Serialization/ModuleFormat.h +++ b/include/swift/Serialization/ModuleFormat.h @@ -55,7 +55,7 @@ const uint16_t VERSION_MAJOR = 0; /// describe what change you made. The content of this comment isn't important; /// it just ensures a conflict if two people change the module format. /// Don't worry about adhering to the 80-column limit for this line. -const uint16_t VERSION_MINOR = 402; // Last change: effects(releasenone) +const uint16_t VERSION_MINOR = 407; // Last change: convert_escape_to_noescape using DeclIDField = BCFixed<31>; diff --git a/lib/IRGen/LoadableByAddress.cpp b/lib/IRGen/LoadableByAddress.cpp index 4d63ace834041..f9e18dc11c930 100644 --- a/lib/IRGen/LoadableByAddress.cpp +++ b/lib/IRGen/LoadableByAddress.cpp @@ -2494,7 +2494,8 @@ void LoadableByAddress::recreateConvInstrs() { case SILInstructionKind::ConvertEscapeToNoEscapeInst: { auto instr = cast(convInstr); newInstr = convBuilder.createConvertEscapeToNoEscape( - instr->getLoc(), instr->getOperand(), newType); + instr->getLoc(), instr->getOperand(), newType, + instr->isLifetimeGuaranteed()); break; } case SILInstructionKind::MarkDependenceInst: { diff --git a/lib/ParseSIL/ParseSIL.cpp b/lib/ParseSIL/ParseSIL.cpp index 402561947f92a..0c4b5c110b6cd 100644 --- a/lib/ParseSIL/ParseSIL.cpp +++ b/lib/ParseSIL/ParseSIL.cpp @@ -2947,6 +2947,10 @@ bool SILParser::parseSILInstruction(SILBuilder &B) { SILType Ty; Identifier ToToken; SourceLoc ToLoc; + bool guaranteed = true; + if (Opcode == SILInstructionKind::ConvertEscapeToNoEscapeInst) + if(parseSILOptional(guaranteed, *this, "not_guaranteed")) + return true; if (parseTypedValueRef(Val, B) || parseSILIdentifier(ToToken, ToLoc, diag::expected_tok_in_sil_instr, "to") || @@ -2980,7 +2984,7 @@ bool SILParser::parseSILInstruction(SILBuilder &B) { ResultVal = B.createConvertFunction(InstLoc, Val, Ty); break; case SILInstructionKind::ConvertEscapeToNoEscapeInst: - ResultVal = B.createConvertEscapeToNoEscape(InstLoc, Val, Ty); + ResultVal = B.createConvertEscapeToNoEscape(InstLoc, Val, Ty, guaranteed); break; case SILInstructionKind::AddressToPointerInst: ResultVal = B.createAddressToPointer(InstLoc, Val, Ty); diff --git a/lib/SIL/SILInstructions.cpp b/lib/SIL/SILInstructions.cpp index 74d7a3cc659c2..dec0573fbc51f 100644 --- a/lib/SIL/SILInstructions.cpp +++ b/lib/SIL/SILInstructions.cpp @@ -1998,7 +1998,7 @@ ConvertFunctionInst::create(SILDebugLocation DebugLoc, SILValue Operand, ConvertEscapeToNoEscapeInst *ConvertEscapeToNoEscapeInst::create( SILDebugLocation DebugLoc, SILValue Operand, SILType Ty, SILFunction &F, - SILOpenedArchetypesState &OpenedArchetypes) { + SILOpenedArchetypesState &OpenedArchetypes, bool isLifetimeGuaranteed) { SILModule &Mod = F.getModule(); SmallVector TypeDependentOperands; collectTypeDependentOperands(TypeDependentOperands, OpenedArchetypes, F, @@ -2007,7 +2007,7 @@ ConvertEscapeToNoEscapeInst *ConvertEscapeToNoEscapeInst::create( totalSizeToAlloc(1 + TypeDependentOperands.size()); void *Buffer = Mod.allocateInst(size, alignof(ConvertEscapeToNoEscapeInst)); auto *CFI = ::new (Buffer) ConvertEscapeToNoEscapeInst( - DebugLoc, Operand, TypeDependentOperands, Ty); + DebugLoc, Operand, TypeDependentOperands, Ty, isLifetimeGuaranteed); // If we do not have lowered SIL, make sure that are not performing // ABI-incompatible conversions. // diff --git a/lib/SIL/SILPrinter.cpp b/lib/SIL/SILPrinter.cpp index df9f1d0bad7f3..290d52ce7e867 100644 --- a/lib/SIL/SILPrinter.cpp +++ b/lib/SIL/SILPrinter.cpp @@ -1435,7 +1435,8 @@ class SILPrinter : public SILInstructionVisitor { printUncheckedConversionInst(CI, CI->getOperand()); } void visitConvertEscapeToNoEscapeInst(ConvertEscapeToNoEscapeInst *CI) { - printUncheckedConversionInst(CI, CI->getOperand()); + *this << (CI->isLifetimeGuaranteed() ? "" : "[not_guaranteed] ") + << getIDAndType(CI->getOperand()) << " to " << CI->getType(); } void visitThinFunctionToPointerInst(ThinFunctionToPointerInst *CI) { printUncheckedConversionInst(CI, CI->getOperand()); diff --git a/lib/SILGen/SILGenBridging.cpp b/lib/SILGen/SILGenBridging.cpp index f2aa34060e382..6d1680a6311d4 100644 --- a/lib/SILGen/SILGenBridging.cpp +++ b/lib/SILGen/SILGenBridging.cpp @@ -925,23 +925,19 @@ SILGenFunction::emitBlockToFunc(SILLocation loc, // Create it in the current function. auto thunkValue = B.createFunctionRef(loc, thunk); - SingleValueInstruction *thunkedFn = B.createPartialApply( + ManagedValue thunkedFn = B.createPartialApply( loc, thunkValue, SILType::getPrimitiveObjectType(substFnTy), subs, - block.forward(*this), + block, SILType::getPrimitiveObjectType(loweredFuncTyWithoutNoEscape)); if (!loweredFuncTy->isNoEscape()) { - return emitManagedRValueWithCleanup(thunkedFn); + return thunkedFn; } // Handle the escaping to noescape conversion. assert(loweredFuncTy->isNoEscape()); - - auto &funcTL = getTypeLowering(loweredFuncTy); - SingleValueInstruction *noEscapeThunkFn = - B.createConvertEscapeToNoEscape(loc, thunkedFn, funcTL.getLoweredType()); - enterPostponedCleanup(thunkedFn); - return emitManagedRValueWithCleanup(noEscapeThunkFn); + return B.createConvertEscapeToNoEscape(loc, thunkedFn, + SILType::getPrimitiveObjectType(loweredFuncTy)); } static ManagedValue emitCBridgedToNativeValue(SILGenFunction &SGF, diff --git a/lib/SILGen/SILGenBuilder.cpp b/lib/SILGen/SILGenBuilder.cpp index fe806718148a9..5ff529fa656be 100644 --- a/lib/SILGen/SILGenBuilder.cpp +++ b/lib/SILGen/SILGenBuilder.cpp @@ -207,6 +207,31 @@ ManagedValue SILGenBuilder::createConvertFunction(SILLocation loc, return cloner.clone(result); } +ManagedValue SILGenBuilder::createConvertEscapeToNoEscape( + SILLocation loc, ManagedValue fn, SILType resultTy, + bool postponeToNoEscapeCleanup) { + + auto fnType = fn.getType().castTo(); + auto resultFnType = resultTy.castTo(); + + // Escaping to noescape conversion. + assert(resultFnType->getRepresentation() == + SILFunctionTypeRepresentation::Thick && + fnType->getRepresentation() == + SILFunctionTypeRepresentation::Thick && + !fnType->isNoEscape() && resultFnType->isNoEscape() && + "Expect a escaping to noescape conversion"); + + SILValue fnValue = postponeToNoEscapeCleanup + ? fn.ensurePlusOne(SGF, loc).forward(SGF) + : fn.getValue(); + SILValue result = createConvertEscapeToNoEscape(loc, fnValue, resultTy, + postponeToNoEscapeCleanup); + if (postponeToNoEscapeCleanup) + getSILGenFunction().enterPostponedCleanup(fnValue); + return ManagedValue::forTrivialObjectRValue(result); +} + ManagedValue SILGenBuilder::createInitExistentialValue( SILLocation loc, SILType existentialType, CanType formalConcreteType, ManagedValue concrete, ArrayRef conformances) { diff --git a/lib/SILGen/SILGenBuilder.h b/lib/SILGen/SILGenBuilder.h index 26cce6f749a54..417a858106782 100644 --- a/lib/SILGen/SILGenBuilder.h +++ b/lib/SILGen/SILGenBuilder.h @@ -351,6 +351,12 @@ class SILGenBuilder : public SILBuilder { ManagedValue createConvertFunction(SILLocation loc, ManagedValue fn, SILType resultTy); + using SILBuilder::createConvertEscapeToNoEscape; + ManagedValue + createConvertEscapeToNoEscape(SILLocation loc, ManagedValue fn, + SILType resultTy, + bool postponeToNoEscapeCleanup = true); + using SILBuilder::createStore; /// Forward \p value into \p address. /// diff --git a/lib/SILGen/SILGenExpr.cpp b/lib/SILGen/SILGenExpr.cpp index 704bf47ef5614..6375f94878d81 100644 --- a/lib/SILGen/SILGenExpr.cpp +++ b/lib/SILGen/SILGenExpr.cpp @@ -2090,8 +2090,11 @@ RValue RValueEmitter::visitFunctionConversionExpr(FunctionConversionExpr *e, if (srcRepTy != srcTy) result = convertFunctionRepresentation(SGF, e, result, srcRepTy, srcTy); - if (srcTy != destTy) - result = SGF.emitTransformedValue(e, result, srcTy, destTy); + if (srcTy != destTy) { + bool postponeToNoEscapeCleanup = !isa(e->getSubExpr()); + result = SGF.emitTransformedValue(e, result, srcTy, destTy, SGFContext(), + postponeToNoEscapeCleanup); + } if (destTy != destRepTy) result = convertFunctionRepresentation(SGF, e, result, destTy, destRepTy); diff --git a/lib/SILGen/SILGenFunction.h b/lib/SILGen/SILGenFunction.h index df5f9aa3f20ae..4df9c3bbf4f72 100644 --- a/lib/SILGen/SILGenFunction.h +++ b/lib/SILGen/SILGenFunction.h @@ -1614,7 +1614,8 @@ class LLVM_LIBRARY_VISIBILITY SILGenFunction ManagedValue emitTransformedValue(SILLocation loc, ManagedValue input, CanType inputType, CanType outputType, - SGFContext ctx = SGFContext()); + SGFContext ctx = SGFContext(), + bool postponeToNoEscapeCleanup = true); /// Most general form of the above. ManagedValue emitTransformedValue(SILLocation loc, ManagedValue input, @@ -1622,7 +1623,8 @@ class LLVM_LIBRARY_VISIBILITY SILGenFunction CanType inputSubstType, AbstractionPattern outputOrigType, CanType outputSubstType, - SGFContext ctx = SGFContext()); + SGFContext ctx = SGFContext(), + bool postponeToNoEscapeCleanup = true); RValue emitTransformedValue(SILLocation loc, RValue &&input, AbstractionPattern inputOrigType, CanType inputSubstType, diff --git a/lib/SILGen/SILGenPoly.cpp b/lib/SILGen/SILGenPoly.cpp index 1726561ea219c..6066980cccbbb 100644 --- a/lib/SILGen/SILGenPoly.cpp +++ b/lib/SILGen/SILGenPoly.cpp @@ -136,7 +136,8 @@ namespace { CanType inputSubstType, AbstractionPattern outputOrigType, CanType outputSubstType, - SGFContext ctxt); + SGFContext ctxt, + bool postponeToNoEscapeCleanup = true); /// Transform a metatype value. ManagedValue transformMetatype(ManagedValue fn, @@ -159,7 +160,8 @@ namespace { CanAnyFunctionType inputSubstType, AbstractionPattern outputOrigType, CanAnyFunctionType outputSubstType, - const TypeLowering &expectedTL); + const TypeLowering &expectedTL, + bool postponeToNoEscapeCleanup = true); }; } // end anonymous namespace ; @@ -356,7 +358,8 @@ ManagedValue Transform::transform(ManagedValue v, CanType inputSubstType, AbstractionPattern outputOrigType, CanType outputSubstType, - SGFContext ctxt) { + SGFContext ctxt, + bool postponeToNoEscapeCleanup) { // Look through inout types. if (isa(inputSubstType)) inputSubstType = CanType(inputSubstType->getInOutObjectType()); @@ -440,7 +443,8 @@ ManagedValue Transform::transform(ManagedValue v, return transformFunction(v, inputOrigType, inputFnType, outputOrigType, outputFnType, - expectedTL); + expectedTL, + postponeToNoEscapeCleanup); } // - tuples of transformable values @@ -2917,22 +2921,20 @@ static ManagedValue createThunk(SILGenFunction &SGF, // Create it in our current function. auto thunkValue = SGF.B.createFunctionRef(loc, thunk); - SingleValueInstruction *thunkedFn = + ManagedValue thunkedFn = SGF.B.createPartialApply(loc, thunkValue, SILType::getPrimitiveObjectType(substFnType), - subs, fn.ensurePlusOne(SGF, loc).forward(SGF), + subs, fn.ensurePlusOne(SGF, loc), SILType::getPrimitiveObjectType(toType)); if (!expectedType->isNoEscape()) { - return SGF.emitManagedRValueWithCleanup(thunkedFn, expectedTL); + return thunkedFn; } // Handle the escaping to noescape conversion. assert(expectedType->isNoEscape()); - SingleValueInstruction *noEscapeThunkFn = SGF.B.createConvertEscapeToNoEscape( - loc, thunkedFn, expectedTL.getLoweredType()); - SGF.enterPostponedCleanup(thunkedFn); - return SGF.emitManagedRValueWithCleanup(noEscapeThunkFn); + return SGF.B.createConvertEscapeToNoEscape(loc, thunkedFn, + SILType::getPrimitiveObjectType(expectedType)); } static CanSILFunctionType buildWithoutActuallyEscapingThunkType( @@ -3128,7 +3130,8 @@ ManagedValue Transform::transformFunction(ManagedValue fn, CanAnyFunctionType inputSubstType, AbstractionPattern outputOrigType, CanAnyFunctionType outputSubstType, - const TypeLowering &expectedTL) { + const TypeLowering &expectedTL, + bool postponeToNoEscapeCleanup) { assert(fn.getType().isObject() && "expected input to emitTransformedFunctionValue to be loaded"); @@ -3186,18 +3189,9 @@ ManagedValue Transform::transformFunction(ManagedValue fn, SGF.B.createThinToThickFunction(Loc, fn.forward(SGF), resTy)); } else if (newFnType != expectedFnType) { // Escaping to noescape conversion. - assert(expectedFnType->getRepresentation() == - SILFunctionTypeRepresentation::Thick && - fnType->getRepresentation() == - SILFunctionTypeRepresentation::Thick && - !fnType->isNoEscape() && expectedFnType->isNoEscape() && - "Expect a escaping to noescape conversion"); SILType resTy = SILType::getPrimitiveObjectType(expectedFnType); - auto fnValue = fn.forward(SGF); - SGF.enterPostponedCleanup(fnValue); - SingleValueInstruction *noEscapeThunkFn = - SGF.B.createConvertEscapeToNoEscape(Loc, fnValue, resTy); - fn = SGF.emitManagedRValueWithCleanup(noEscapeThunkFn); + fn = SGF.B.createConvertEscapeToNoEscape(Loc, fn, resTy, + postponeToNoEscapeCleanup); } return fn; @@ -3293,11 +3287,12 @@ ManagedValue SILGenFunction::emitTransformedValue(SILLocation loc, ManagedValue v, CanType inputType, CanType outputType, - SGFContext ctxt) { + SGFContext ctxt, + bool postponeToNoEscapeCleanup) { return emitTransformedValue(loc, v, AbstractionPattern(inputType), inputType, AbstractionPattern(outputType), outputType, - ctxt); + ctxt, postponeToNoEscapeCleanup); } ManagedValue @@ -3306,12 +3301,14 @@ SILGenFunction::emitTransformedValue(SILLocation loc, ManagedValue v, CanType inputSubstType, AbstractionPattern outputOrigType, CanType outputSubstType, - SGFContext ctxt) { + SGFContext ctxt, + bool postponeToNoEscapeCleanup) { return Transform(*this, loc).transform(v, inputOrigType, inputSubstType, outputOrigType, - outputSubstType, ctxt); + outputSubstType, ctxt, + postponeToNoEscapeCleanup); } RValue diff --git a/lib/SILOptimizer/IPO/ClosureSpecializer.cpp b/lib/SILOptimizer/IPO/ClosureSpecializer.cpp index ebcce9be05360..6beb3d7878a3f 100644 --- a/lib/SILOptimizer/IPO/ClosureSpecializer.cpp +++ b/lib/SILOptimizer/IPO/ClosureSpecializer.cpp @@ -662,8 +662,8 @@ SILValue ClosureSpecCloner::cloneCalleeConversion(SILValue calleeValue, auto *Cvt = cast(calleeValue); calleeValue = cloneCalleeConversion(Cvt->getOperand(), NewClosure, Builder); - return Builder.createConvertEscapeToNoEscape(CallSiteDesc.getLoc(), - calleeValue, Cvt->getType()); + return Builder.createConvertEscapeToNoEscape( + CallSiteDesc.getLoc(), calleeValue, Cvt->getType(), true); } /// \brief Populate the body of the cloned closure, modifying instructions as diff --git a/lib/SILOptimizer/Mandatory/DefiniteInitialization.cpp b/lib/SILOptimizer/Mandatory/DefiniteInitialization.cpp index 82f1ef125be26..352421009b1d3 100644 --- a/lib/SILOptimizer/Mandatory/DefiniteInitialization.cpp +++ b/lib/SILOptimizer/Mandatory/DefiniteInitialization.cpp @@ -2951,6 +2951,209 @@ static bool lowerRawSILOperations(SILFunction &Fn) { return Changed; } +static SILBasicBlock *getOptionalDiamondSuccessor(SwitchEnumInst *SEI) { + auto numSuccs = SEI->getNumSuccessors(); + if (numSuccs != 2) + return nullptr; + auto *SuccSome = SEI->getCase(0).second; + auto *SuccNone = SEI->getCase(1).second; + if (SuccSome->args_size() != 1) + std::swap(SuccSome, SuccNone); + + if (SuccSome->args_size() != 1 || SuccNone->args_size() != 0) + return nullptr; + + auto *Succ = SuccSome->getSingleSuccessorBlock(); + if (!Succ) + return nullptr; + + if (SuccNone == Succ) + return Succ; + + SuccNone = SuccNone->getSingleSuccessorBlock(); + if (SuccNone == Succ) + return Succ; + + SuccNone = SuccNone->getSingleSuccessorBlock(); + if (SuccNone == Succ) + return Succ; + + return nullptr; +} + +/// Extend the lifetime of the convert_escape_to_noescape's operand to the end +/// of the function. +void extendLifetimeToEndOfFunction(SILFunction &Fn, + ConvertEscapeToNoEscapeInst *Cvt) { + auto EscapingClosure = Cvt->getOperand(); + auto EscapingClosureTy = EscapingClosure->getType(); + auto OptionalEscapingClosureTy = SILType::getOptionalType(EscapingClosureTy); + auto loc = RegularLocation::getAutoGeneratedLocation(); + + SILBuilderWithScope B(Cvt); + auto NewCvt = B.createConvertEscapeToNoEscape( + Cvt->getLoc(), Cvt->getOperand(), Cvt->getType(), true); + Cvt->replaceAllUsesWith(NewCvt); + Cvt->eraseFromParent(); + Cvt = NewCvt; + + // Create an alloc_stack Optional<() -> ()> at the beginning of the function. + AllocStackInst *Slot; + auto &Context = Cvt->getModule().getASTContext(); + { + SILBuilderWithScope B(Fn.getEntryBlock()->begin()); + Slot = B.createAllocStack(loc, OptionalEscapingClosureTy); + auto *NoneDecl = Context.getOptionalNoneDecl(); + // Store None to it. + B.createStore( + loc, B.createEnum(loc, SILValue(), NoneDecl, OptionalEscapingClosureTy), + Slot, StoreOwnershipQualifier::Init); + } + // Insert a copy before the convert_escape_to_noescape and store it to the + // alloc_stack location. + { + SILBuilderWithScope B(Cvt); + auto *SomeDecl = Context.getOptionalSomeDecl(); + B.createDestroyAddr(loc, Slot); + auto ClosureCopy = B.createCopyValue(loc, EscapingClosure); + B.createStore( + loc, + B.createEnum(loc, ClosureCopy, SomeDecl, OptionalEscapingClosureTy), + Slot, StoreOwnershipQualifier::Init); + } + // Insert destroys at the function exits. + SmallVector ExitingBlocks; + Fn.findExitingBlocks(ExitingBlocks); + for (auto *Exit : ExitingBlocks) { + auto *Term = Exit->getTerminator(); + SILBuilderWithScope B(Term); + B.setInsertionPoint(Term); + B.createDestroyAddr(loc, Slot); + B.createDeallocStack(loc, Slot); + } +} + +/// Ensure the lifetime of the closure accross an +/// +/// optional<@escaping () -> ()> to +/// optional<@noescape @convention(block) () -> ()> +/// +/// conversion and its use. +/// +/// The pattern this is looking for +/// switch_enum %closure +/// / \ +/// convert_escape_to_noescape nil +/// switch_enum +/// / \ +/// convertToBlock nil +/// \ / +/// (%convertOptionalBlock :) +/// We will insert a copy_value of the original %closure before the two +/// diamonds. And a destroy of %closure at the last destroy of +/// %convertOptionalBlock. +static bool trySwitchEnumPeephole(ConvertEscapeToNoEscapeInst *Cvt) { + auto *blockArg = dyn_cast(Cvt->getOperand()); + if (!blockArg) + return false; + auto *PredBB = Cvt->getParent()->getSinglePredecessorBlock(); + if (!PredBB) + return false; + auto *ConvertSuccessorBlock = Cvt->getParent()->getSingleSuccessorBlock(); + if (!ConvertSuccessorBlock) + return false; + auto *SwitchEnum1 = dyn_cast(PredBB->getTerminator()); + if (!SwitchEnum1) + return false; + auto *DiamondSucc = getOptionalDiamondSuccessor(SwitchEnum1); + if (!DiamondSucc) + return false; + auto *SwitchEnum2 = dyn_cast(DiamondSucc->getTerminator()); + if (!SwitchEnum2) + return false; + auto *DiamondSucc2 = getOptionalDiamondSuccessor(SwitchEnum2); + if (!DiamondSucc2) + return false; + if (DiamondSucc2->getNumArguments() != 1) + return false; + + // Look for the last and only destroy. + SILInstruction *onlyDestroy = [&]() -> SILInstruction * { + SILInstruction *lastDestroy = nullptr; + for (auto *Use : DiamondSucc2->getArgument(0)->getUses()) { + SILInstruction *Usr = Use->getUser(); + if (isa(Usr) || isa(Usr) || + isa(Usr)) { + if (lastDestroy) + return nullptr; + lastDestroy = Usr; + } + } + return lastDestroy; + }(); + if (!onlyDestroy) + return false; + + // Replace the convert_escape_to_noescape instruction. + { + SILBuilderWithScope B(Cvt); + auto NewCvt = B.createConvertEscapeToNoEscape( + Cvt->getLoc(), Cvt->getOperand(), Cvt->getType(), true); + Cvt->replaceAllUsesWith(NewCvt); + Cvt->eraseFromParent(); + Cvt = NewCvt; + } + + // Extend the lifetime. + SILBuilderWithScope B(SwitchEnum1); + auto loc = RegularLocation::getAutoGeneratedLocation(); + auto copy = + B.createCopyValue(loc, SwitchEnum1->getOperand()); + B.setInsertionPoint(onlyDestroy); + B.createDestroyValue(loc, copy); + return true; +} + +llvm::cl::opt DIDisableConvertEscapeToNoEscapeSwitchEnumPeephole( + "sil-di-disable-convert-escape-to-noescape-switch-peephole", + llvm::cl::init(false), + llvm::cl::desc( + "Disable the convert_escape_to_noescape switch enum peephole. "), + llvm::cl::Hidden); + +/// Fix-up the lifetime of the escaping closure argument of +/// convert_escape_to_noescape [not_guaranteed] instructions. +/// +/// convert_escape_to_noescape [not_guaranteed] assume that someone guarantees +/// the lifetime of the operand for the duration of the trivial closure result. +/// SILGen does not guarantee this for '[not_guaranteed]' instructions so we +/// ensure it here. +static bool fixupConvertEscapeToNoEscapeLifetime(SILFunction &Fn) { + bool Changed = false; + for (auto &BB : Fn) { + auto I = BB.begin(); + while (I != BB.end()) { + SILInstruction *Inst = &*I; + ++I; + auto *Cvt = dyn_cast(Inst); + if (!Cvt || Cvt->isLifetimeGuaranteed()) + continue; + + // First try to peephole a known pattern. + if (!DIDisableConvertEscapeToNoEscapeSwitchEnumPeephole && + trySwitchEnumPeephole(Cvt)) { + Changed |= true; + continue; + } + + // Otherwise, extend the lifetime of the operand to the end of the + // function. + extendLifetimeToEndOfFunction(Fn, Cvt); + Changed |= true; + } + } + return Changed; +} namespace { /// Perform definitive initialization analysis and promote alloc_box uses into @@ -2973,6 +3176,11 @@ class DefiniteInitialization : public SILFunctionTransform { // Lower raw-sil only instructions used by this pass, like "assign". if (lowerRawSILOperations(*getFunction())) invalidateAnalysis(SILAnalysis::InvalidationKind::FunctionBody); + + // Fixup lifetimes of optional convertEscapeToNoEscape. + if (fixupConvertEscapeToNoEscapeLifetime(*getFunction())) + invalidateAnalysis(SILAnalysis::InvalidationKind::FunctionBody); + } }; diff --git a/lib/Serialization/DeserializeSIL.cpp b/lib/Serialization/DeserializeSIL.cpp index 4fe2d87f00817..0da61ebad4da8 100644 --- a/lib/Serialization/DeserializeSIL.cpp +++ b/lib/Serialization/DeserializeSIL.cpp @@ -1066,7 +1066,6 @@ bool SILDeserializer::readSILInstruction(SILFunction *Fn, SILBasicBlock *BB, ONEOPERAND_ONETYPE_INST(ObjCMetatypeToObject) ONEOPERAND_ONETYPE_INST(ObjCExistentialMetatypeToObject) ONEOPERAND_ONETYPE_INST(ConvertFunction) - ONEOPERAND_ONETYPE_INST(ConvertEscapeToNoEscape) ONEOPERAND_ONETYPE_INST(ThinFunctionToPointer) ONEOPERAND_ONETYPE_INST(PointerToThinFunction) ONEOPERAND_ONETYPE_INST(ProjectBlockStorage) @@ -1082,6 +1081,18 @@ bool SILDeserializer::readSILInstruction(SILFunction *Fn, SILBasicBlock *BB, TyID); break; } + case SILInstructionKind::ConvertEscapeToNoEscapeInst: { + assert(RecordKind == SIL_ONE_TYPE_ONE_OPERAND && + "Layout should be OneTypeOneOperand."); + bool isLifetimeGuaranteed = Attr & 0x01; + ResultVal = Builder.createConvertEscapeToNoEscape( + Loc, + getLocalValue(ValID, getSILType(MF->getType(TyID2), + (SILValueCategory)TyCategory2)), + getSILType(MF->getType(TyID), (SILValueCategory)TyCategory), + isLifetimeGuaranteed); + break; + } case SILInstructionKind::PointerToAddressInst: { assert(RecordKind == SIL_ONE_TYPE_ONE_OPERAND && diff --git a/lib/Serialization/SerializeSIL.cpp b/lib/Serialization/SerializeSIL.cpp index cd0d181a059ce..6383ca4cc37c7 100644 --- a/lib/Serialization/SerializeSIL.cpp +++ b/lib/Serialization/SerializeSIL.cpp @@ -250,7 +250,8 @@ namespace { void writeSILBlock(const SILModule *SILMod); void writeIndexTables(); - void writeConversionLikeInstruction(const SingleValueInstruction *I); + void writeConversionLikeInstruction(const SingleValueInstruction *I, + bool guaranteed); void writeOneTypeLayout(SILInstructionKind valueKind, SILType type); void writeOneTypeOneOperandLayout(SILInstructionKind valueKind, unsigned attrs, @@ -579,10 +580,18 @@ void SILSerializer::writeOneTypeOneOperandLayout(SILInstructionKind valueKind, /// Write an instruction that looks exactly like a conversion: all /// important information is encoded in the operand and the result type. -void SILSerializer::writeConversionLikeInstruction(const SingleValueInstruction *I) { +__attribute__((noinline)) +static unsigned getZeroOrOne(bool getOne) { + if (getOne) + return 0x1; + return 0x0; +} + +void SILSerializer::writeConversionLikeInstruction( + const SingleValueInstruction *I, bool guaranteed) { assert(I->getNumOperands() - I->getTypeDependentOperands().size() == 1); - writeOneTypeOneOperandLayout(I->getKind(), 0, I->getType(), - I->getOperand(0)); + writeOneTypeOneOperandLayout(I->getKind(), getZeroOrOne(guaranteed), + I->getType(), I->getOperand(0)); } void @@ -1447,7 +1456,11 @@ void SILSerializer::writeSILInstruction(const SILInstruction &SI) { case SILInstructionKind::ObjCMetatypeToObjectInst: case SILInstructionKind::ObjCExistentialMetatypeToObjectInst: case SILInstructionKind::ProjectBlockStorageInst: { - writeConversionLikeInstruction(cast(&SI)); + bool guaranteed = false; + if (SI.getKind() == SILInstructionKind::ConvertEscapeToNoEscapeInst) + guaranteed = cast(SI).isLifetimeGuaranteed(); + writeConversionLikeInstruction(cast(&SI), + guaranteed); break; } case SILInstructionKind::PointerToAddressInst: { diff --git a/test/SILGen/Inputs/usr/include/BridgeTestFoundation.h b/test/SILGen/Inputs/usr/include/BridgeTestFoundation.h index 456b446659aff..2ac7d33af2ab3 100644 --- a/test/SILGen/Inputs/usr/include/BridgeTestFoundation.h +++ b/test/SILGen/Inputs/usr/include/BridgeTestFoundation.h @@ -82,6 +82,9 @@ void nonnullSetBlockResult(NSSet *_Nonnull (^block)(void)); void noescapeBlock(__attribute__((noescape)) void (^block)(void)); void escapeBlock(void (^block)(void)); +void noescapeBlock3(__attribute__((noescape)) void (^block)(NSString *s), + __attribute__((noescape)) void (^block2)(NSString *s), + NSString *f); void noescapeNonnullBlock(__attribute__((noescape)) void (^_Nonnull block)(void)); void escapeNonnullBlock(void (^_Nonnull block)(void)); diff --git a/test/SILGen/objc_blocks_bridging.swift b/test/SILGen/objc_blocks_bridging.swift index 114f46c24416c..650ca7de7c28a 100644 --- a/test/SILGen/objc_blocks_bridging.swift +++ b/test/SILGen/objc_blocks_bridging.swift @@ -174,9 +174,9 @@ func bridgeNonnullBlockResult() { nonnullStringBlockResult { return "test" } } -// CHECK-LABEL: sil hidden @$S20objc_blocks_bridging19bridgeNoescapeBlock2fnyyyXE_tF -func bridgeNoescapeBlock(fn: () -> ()) { - // CHECK: [[CLOSURE_FN:%.*]] = function_ref @$S20objc_blocks_bridging19bridgeNoescapeBlock2fnyyyXE_tFyyXEfU_ +// CHECK-LABEL: sil hidden @$S20objc_blocks_bridging19bridgeNoescapeBlock2fn5optFnyyyXE_yycSgtF +func bridgeNoescapeBlock(fn: () -> (), optFn: (() -> ())?) { + // CHECK: [[CLOSURE_FN:%.*]] = function_ref @$S20objc_blocks_bridging19bridgeNoescapeBlock2fn5optFnyyyXE_yycSgtFyyXEfU_ // CHECK: [[CONV_FN:%.*]] = convert_function [[CLOSURE_FN]] // CHECK: [[THICK_FN:%.*]] = thin_to_thick_function [[CONV_FN]] // CHECK: [[BLOCK_ALLOC:%.*]] = alloc_stack $@block_storage @noescape @callee_guaranteed () -> () @@ -216,7 +216,7 @@ func bridgeNoescapeBlock(fn: () -> ()) { // CHECK: apply [[FN]]([[NIL_BLOCK]]) noescapeBlock(nil) - // CHECK: [[CLOSURE_FN:%.*]] = function_ref @$S20objc_blocks_bridging19bridgeNoescapeBlock2fnyyyXE_tFyyXEfU0_ + // CHECK: [[CLOSURE_FN:%.*]] = function_ref @$S20objc_blocks_bridging19bridgeNoescapeBlock2fn5optFnyyyXE_yycSgtF // CHECK: [[CONV_FN:%.*]] = convert_function [[CLOSURE_FN]] // CHECK: [[THICK_FN:%.*]] = thin_to_thick_function [[CONV_FN]] // CHECK: [[BLOCK_ALLOC:%.*]] = alloc_stack $@block_storage @noescape @callee_guaranteed () -> () @@ -246,6 +246,8 @@ func bridgeNoescapeBlock(fn: () -> ()) { // CHECK: apply [[FN]]([[BLOCK]]) noescapeNonnullBlock(fn) + noescapeBlock(optFn) + noescapeBlockAlias { } noescapeBlockAlias(fn) noescapeBlockAlias(nil) @@ -254,6 +256,18 @@ func bridgeNoescapeBlock(fn: () -> ()) { noescapeNonnullBlockAlias(fn) } +public func bridgeNoescapeBlock( optFn: ((String?) -> ())?, optFn2: ((String?) -> ())?) { + noescapeBlock3(optFn, optFn2, "Foobar") +} + + +@_silgen_name("_returnOptionalEscape") +public func returnOptionalEscape() -> (() ->())? + +public func bridgeNoescapeBlock() { + noescapeBlock(returnOptionalEscape()) +} + class ObjCClass : NSObject {} extension ObjCClass { diff --git a/test/SILGen/witnesses.swift b/test/SILGen/witnesses.swift index a0aabeca3e345..f8921384b13c7 100644 --- a/test/SILGen/witnesses.swift +++ b/test/SILGen/witnesses.swift @@ -519,7 +519,11 @@ protocol EscapingReq { // CHECK-LABEL: sil private [transparent] [thunk] @$S9witnesses18EscapingCovarianceVAA0B3ReqA2aDP1fyyS2icFTW : $@convention(witness_method: EscapingReq) (@owned @callee_guaranteed (Int) -> Int, @in_guaranteed EscapingCovariance) -> () // CHECK-NOT: return +// CHECK-NOT: copy_value // CHECK: convert_escape_to_noescape %0 +// CHECK-NOT: copy_value +// CHECK: destroy_value %0 +// CHECK: return struct EscapingCovariance: EscapingReq { func f(_: (Int) -> Int) { } } diff --git a/test/SILOptimizer/Inputs/Closure.h b/test/SILOptimizer/Inputs/Closure.h new file mode 100644 index 0000000000000..1929dcae4c3cb --- /dev/null +++ b/test/SILOptimizer/Inputs/Closure.h @@ -0,0 +1,6 @@ +#import + +void noescapeBlock(__attribute__((noescape)) void (^block)(void)); +void noescapeBlock3(__attribute__((noescape)) void (^block)(NSString *s), + __attribute__((noescape)) void (^block2)(NSString *s), + NSString *f); diff --git a/test/SILOptimizer/definite-init-convert-to-escape.swift b/test/SILOptimizer/definite-init-convert-to-escape.swift new file mode 100644 index 0000000000000..ee7cd8a246c89 --- /dev/null +++ b/test/SILOptimizer/definite-init-convert-to-escape.swift @@ -0,0 +1,96 @@ +// RUN: %target-swift-frontend -module-name A -verify -emit-sil -import-objc-header %S/Inputs/Closure.h -disable-objc-attr-requires-foundation-module -enable-sil-ownership %s | %FileCheck %s +// RUN: %target-swift-frontend -module-name A -verify -emit-sil -import-objc-header %S/Inputs/Closure.h -disable-objc-attr-requires-foundation-module -enable-sil-ownership -Xllvm -sil-di-disable-convert-escape-to-noescape-switch-peephole %s | %FileCheck %s --check-prefix=NOPEEPHOLE + +// REQUIRES: objc_interop + +import Foundation + +// Make sure that we keep the escaping closures alive accross the ultimate call. +// CHECK-LABEL: sil @$S1A19bridgeNoescapeBlock5optFn0D3Fn2yySSSgcSg_AFtF +// CHECK: bb0 +// CHECK: alloc_stack +// CHECK: alloc_stack +// CHECK: retain_value %0 +// CHECK: bb2 +// CHECK: destroy_addr +// CHECK: enum +// CHECK: store +// CHECK: convert_escape_to_noescape % +// CHECK-NOT: strong_release +// CHECK: bb6 +// CHECK: retain_value %1 +// CHECK: bb8 +// CHECK: destroy_addr +// CHECK: enum +// CHECK: store +// CHECK: convert_escape_to_noescape % +// CHECK-NOT: strong_release +// CHECK: br +// CHECK: bb12 +// CHECK: [[F:%.*]] = function_ref @noescapeBlock3 +// CHECK: apply [[F]] +// CHECK: release_value {{.*}} : $Optional +// CHECK: release_value {{.*}} : $Optional<@convention(block) @noescape (Optional) -> ()> +// CHECK: release_value {{.*}} : $Optional<@convention(block) @noescape (Optional) +// CHECK: release_value %1 : $Optional<@callee_guaranteed (@owned Optional) -> ()> +// CHECK: release_value %0 : $Optional<@callee_guaranteed (@owned Optional) -> ()> +// CHECK: destroy_addr {{.*}}Optional<@callee_guaranteed +// CHECK: dealloc_stack +// CHECK: destroy_addr {{.*}}Optional<@callee_guaranteed +// CHECK: dealloc_stack +public func bridgeNoescapeBlock( optFn: ((String?) -> ())?, optFn2: ((String?) -> ())?) { + noescapeBlock3(optFn, optFn2, "Foobar") +} + + +@_silgen_name("_returnOptionalEscape") +public func returnOptionalEscape() -> (() ->())? + +// Make sure that we keep the escaping closure alive accross the ultimate call. + +// CHECK-LABEL: sil @$S1A19bridgeNoescapeBlockyyF : $@convention(thin) () -> () { +// CHECK: bb0: +// CHECK: [[SLOT:%.*]] = alloc_stack $Optional<@callee_guaranteed () -> ()> +// CHECK: [[NONE:%.*]] = enum $Optional +// CHECK: store [[NONE]] to [[SLOT]] +// CHECK: [[V0:%.*]] = function_ref @_returnOptionalEscape +// CHECK: [[V1:%.*]] = apply [[V0]] +// CHECK: bb2: +// CHECK: [[V2:%.*]] = unchecked_enum_data +// CHECK: destroy_addr [[SLOT]] +// CHECK: [[SOME:%.*]] = enum $Optional<@callee_guaranteed () -> ()>, #Optional.some!enumelt.1, [[V2]] +// CHECK: store [[SOME]] to [[SLOT]] +// CHECK: convert_escape_to_noescape % +// CHECK-NOT: strong_release +// CHECK: br +// CHECK: bb6({{.*}} : $Optional<@convention(block) @noescape () -> ()>) +// CHECK: [[F:%.*]] = function_ref @noescapeBlock +// CHECK: apply [[F]]({{.*}}) +// CHECK: destroy_addr [[SLOT]] +// CHECK: dealloc_stack [[SLOT]] + +// NOPEEPHOLE-LABEL: sil @$S1A19bridgeNoescapeBlockyyF : $@convention(thin) () -> () { +// NOPEEPHOLE: bb0: +// NOPEEPHOLE: [[SLOT:%.*]] = alloc_stack $Optional<@callee_guaranteed () -> ()> +// NOPEEPHOLE: [[NONE:%.*]] = enum $Optional +// NOPEEPHOLE: store [[NONE]] to [[SLOT]] +// NOPEEPHOLE: [[V0:%.*]] = function_ref @_returnOptionalEscape +// NOPEEPHOLE: [[V1:%.*]] = apply [[V0]] +// NOPEEPHOLE: bb2: +// NOPEEPHOLE: [[V2:%.*]] = unchecked_enum_data +// NOPEEPHOLE: destroy_addr [[SLOT]] +// NOPEEPHOLE: [[SOME:%.*]] = enum $Optional<@callee_guaranteed () -> ()>, #Optional.some!enumelt.1, [[V2]] +// NOPEEPHOLE: store [[SOME]] to [[SLOT]] +// NOPEEPHOLE: convert_escape_to_noescape % +// NOPEEPHOLE-NOT: strong_release +// NOPEEPHOLE: br +// NOPEEPHOLE: bb6({{.*}} : $Optional<@convention(block) @noescape () -> ()>) +// NOPEEPHOLE: [[F:%.*]] = function_ref @noescapeBlock +// NOPEEPHOLE: apply [[F]]({{.*}}) +// NOPEEPHOLE: destroy_addr [[SLOT]] +// NOPEEPHOLE: dealloc_stack [[SLOT]] +public func bridgeNoescapeBlock() { + noescapeBlock(returnOptionalEscape()) +} + +