diff --git a/docs/SIL.rst b/docs/SIL.rst index 34143b4724f97..40ad81081936e 100644 --- a/docs/SIL.rst +++ b/docs/SIL.rst @@ -2238,6 +2238,7 @@ alloc_ref :: sil-instruction ::= 'alloc_ref' + ('[' 'unique' ']')? ('[' 'objc' ']')? ('[' 'stack' ']')? ('[' 'tail_elems' sil-type '*' sil-operand ']')* @@ -2273,6 +2274,11 @@ The instructions ``ref_tail_addr`` and ``tail_addr`` can be used to project the tail elements. The ``objc`` attribute cannot be used together with ``tail_elems``. +The optional ``unique`` attribute indicates that this is the only reference to +the allocated object. In other words, the reference will not escape or be +written to dynamically. This allows access of the object to be static instead +of dynamic. + alloc_ref_dynamic ````````````````` :: diff --git a/include/swift/SIL/InstructionUtils.h b/include/swift/SIL/InstructionUtils.h index af2bda9b68365..4f2ca28e63dbd 100644 --- a/include/swift/SIL/InstructionUtils.h +++ b/include/swift/SIL/InstructionUtils.h @@ -173,6 +173,14 @@ struct PolymorphicBuiltinSpecializedOverloadInfo { /// return SILValue(). SILValue getStaticOverloadForSpecializedPolymorphicBuiltin(BuiltinInst *bi); +SILFunction *getDestructor(AllocRefInstBase *ari); + +/// \returns None if \p ref is unique. Otherwise, returns an error message in +/// the form of a string. If \p verbose is true, will print out diagnostics, +/// such as the reason for failure. This is helpful when the funciton is being +/// used by the SILVerifier. +Optional isReferenceUnique(AllocRefInstBase *ref, bool verbose); + } // end namespace swift #endif diff --git a/include/swift/SIL/MemAccessUtils.h b/include/swift/SIL/MemAccessUtils.h index cb4c918ab1384..5b63211d765fb 100644 --- a/include/swift/SIL/MemAccessUtils.h +++ b/include/swift/SIL/MemAccessUtils.h @@ -362,6 +362,9 @@ class AccessedStorage { bool isLetAccess(SILFunction *F) const; bool isUniquelyIdentified() const { + if (auto *ref = dyn_cast(value)) + return ref->isUniqueReference(); + switch (getKind()) { case Box: case Stack: diff --git a/include/swift/SIL/SILBuilder.h b/include/swift/SIL/SILBuilder.h index 10422ec523278..32cdcb6e2196c 100644 --- a/include/swift/SIL/SILBuilder.h +++ b/include/swift/SIL/SILBuilder.h @@ -421,17 +421,17 @@ class SILBuilder { Var, hasDynamicLifetime)); } - AllocRefInst *createAllocRef(SILLocation Loc, SILType ObjectType, - bool objc, bool canAllocOnStack, + AllocRefInst *createAllocRef(SILLocation Loc, SILType ObjectType, bool objc, + bool canAllocOnStack, bool isUnique, ArrayRef ElementTypes, ArrayRef ElementCountOperands) { // AllocRefInsts expand to function calls and can therefore not be // counted towards the function prologue. assert(!Loc.isInPrologue()); - return insert(AllocRefInst::create(getSILDebugLocation(Loc), getFunction(), - ObjectType, objc, canAllocOnStack, - ElementTypes, ElementCountOperands, - C.OpenedArchetypes)); + return insert(AllocRefInst::create( + getSILDebugLocation(Loc), getFunction(), ObjectType, objc, + canAllocOnStack, isUnique, ElementTypes, ElementCountOperands, + C.OpenedArchetypes)); } AllocRefDynamicInst *createAllocRefDynamic(SILLocation Loc, SILValue operand, diff --git a/include/swift/SIL/SILCloner.h b/include/swift/SIL/SILCloner.h index 24d4fadca8dbe..7f9841a49e058 100644 --- a/include/swift/SIL/SILCloner.h +++ b/include/swift/SIL/SILCloner.h @@ -817,10 +817,9 @@ SILCloner::visitAllocRefInst(AllocRefInst *Inst) { for (SILType OrigElemType : Inst->getTailAllocatedTypes()) { ElemTypes.push_back(getOpType(OrigElemType)); } - auto *NewInst = getBuilder().createAllocRef(getOpLocation(Inst->getLoc()), - getOpType(Inst->getType()), - Inst->isObjC(), Inst->canAllocOnStack(), - ElemTypes, CountArgs); + auto *NewInst = getBuilder().createAllocRef( + getOpLocation(Inst->getLoc()), getOpType(Inst->getType()), Inst->isObjC(), + Inst->canAllocOnStack(), Inst->isUniqueReference(), ElemTypes, CountArgs); recordClonedInstruction(Inst, NewInst); } diff --git a/include/swift/SIL/SILInstruction.h b/include/swift/SIL/SILInstruction.h index d57e8454a096e..b0a4d68c62f5a 100644 --- a/include/swift/SIL/SILInstruction.h +++ b/include/swift/SIL/SILInstruction.h @@ -1510,12 +1510,9 @@ class AllocStackInst final /// elements, the remaining operands are opened archetype operands. class AllocRefInstBase : public AllocationInst { protected: - - AllocRefInstBase(SILInstructionKind Kind, - SILDebugLocation DebugLoc, - SILType ObjectType, - bool objc, bool canBeOnStack, - ArrayRef ElementTypes); + AllocRefInstBase(SILInstructionKind Kind, SILDebugLocation DebugLoc, + SILType ObjectType, bool objc, bool canBeOnStack, + bool isUnique, ArrayRef ElementTypes); SILType *getTypeStorage(); const SILType *getTypeStorage() const { @@ -1535,6 +1532,14 @@ class AllocRefInstBase : public AllocationInst { SILInstruction::Bits.AllocRefInstBase.OnStack = OnStack; } + bool isUniqueReference() const { + return SILInstruction::Bits.AllocRefInstBase.uniqueReference; + } + + void setUniqueReference(bool isUnique = true) { + SILInstruction::Bits.AllocRefInstBase.uniqueReference = isUnique; + } + ArrayRef getTailAllocatedTypes() const { return {getTypeStorage(), getNumTailTypes()}; } @@ -1573,22 +1578,20 @@ class AllocRefInst final friend AllocRefInstBase; friend SILBuilder; - AllocRefInst(SILDebugLocation DebugLoc, SILFunction &F, - SILType ObjectType, - bool objc, bool canBeOnStack, - ArrayRef ElementTypes, - ArrayRef AllOperands) + AllocRefInst(SILDebugLocation DebugLoc, SILFunction &F, SILType ObjectType, + bool objc, bool canBeOnStack, bool isUnique, + ArrayRef ElementTypes, ArrayRef AllOperands) : InstructionBaseWithTrailingOperands(AllOperands, DebugLoc, ObjectType, - objc, canBeOnStack, ElementTypes) { + objc, canBeOnStack, isUnique, + ElementTypes) { assert(AllOperands.size() >= ElementTypes.size()); std::uninitialized_copy(ElementTypes.begin(), ElementTypes.end(), getTrailingObjects()); } static AllocRefInst *create(SILDebugLocation DebugLoc, SILFunction &F, - SILType ObjectType, - bool objc, bool canBeOnStack, - ArrayRef ElementTypes, + SILType ObjectType, bool objc, bool canBeOnStack, + bool isUnique, ArrayRef ElementTypes, ArrayRef ElementCountOperands, SILOpenedArchetypesState &OpenedArchetypes); @@ -1616,13 +1619,11 @@ class AllocRefDynamicInst final friend AllocRefInstBase; friend SILBuilder; - AllocRefDynamicInst(SILDebugLocation DebugLoc, - SILType ty, - bool objc, + AllocRefDynamicInst(SILDebugLocation DebugLoc, SILType ty, bool objc, ArrayRef ElementTypes, ArrayRef AllOperands) : InstructionBaseWithTrailingOperands(AllOperands, DebugLoc, ty, objc, - false, ElementTypes) { + false, false, ElementTypes) { assert(AllOperands.size() >= ElementTypes.size() + 1); std::uninitialized_copy(ElementTypes.begin(), ElementTypes.end(), getTrailingObjects()); diff --git a/include/swift/SIL/SILNode.h b/include/swift/SIL/SILNode.h index 6f1f50a2dc453..4ae3d78e6a129 100644 --- a/include/swift/SIL/SILNode.h +++ b/include/swift/SIL/SILNode.h @@ -201,12 +201,11 @@ class alignas(8) SILNode { NumOperands : 32-NumAllocationInstBits, VarInfo : 32 ); - IBWTO_BITFIELD(AllocRefInstBase, AllocationInst, 32-NumAllocationInstBits, - ObjC : 1, - OnStack : 1, - NumTailTypes : 32-1-1-NumAllocationInstBits - ); - static_assert(32-1-1-NumAllocationInstBits >= 16, "Reconsider bitfield use?"); + IBWTO_BITFIELD(AllocRefInstBase, AllocationInst, 32 - NumAllocationInstBits, + ObjC : 1, OnStack : 1, uniqueReference : 1, + NumTailTypes : 32 - 1 - 1 - 1 - NumAllocationInstBits); + static_assert(32 - 1 - 1 - 1 - NumAllocationInstBits >= 16, + "Reconsider bitfield use?"); UIWTDOB_BITFIELD_EMPTY(AllocValueBufferInst, AllocationInst); diff --git a/include/swift/SILOptimizer/PassManager/Passes.def b/include/swift/SILOptimizer/PassManager/Passes.def index 82f18120c6038..072d980045ed7 100644 --- a/include/swift/SILOptimizer/PassManager/Passes.def +++ b/include/swift/SILOptimizer/PassManager/Passes.def @@ -279,6 +279,8 @@ IRGEN_PASS(LoadableByAddress, "loadable-address", "SIL Large Loadable type by-address lowering.") PASS(MandatorySILLinker, "mandatory-linker", "Deserialize all referenced SIL functions that are shared or transparent") +PASS(MarkReferenceUnique, "mark-reference-unique", + "If possible, mark alloc_ref instruction with 'unique' flag") PASS(PerformanceSILLinker, "performance-linker", "Deserialize all referenced SIL functions") PASS(RawSILInstLowering, "raw-sil-inst-lowering", diff --git a/lib/SIL/IR/SILInstructions.cpp b/lib/SIL/IR/SILInstructions.cpp index 8ad354575fca6..d65948e38b515 100644 --- a/lib/SIL/IR/SILInstructions.cpp +++ b/lib/SIL/IR/SILInstructions.cpp @@ -200,13 +200,13 @@ DeallocStackInst *AllocStackInst::getSingleDeallocStack() const { } AllocRefInstBase::AllocRefInstBase(SILInstructionKind Kind, - SILDebugLocation Loc, - SILType ObjectType, - bool objc, bool canBeOnStack, + SILDebugLocation Loc, SILType ObjectType, + bool objc, bool canBeOnStack, bool isUnique, ArrayRef ElementTypes) : AllocationInst(Kind, Loc, ObjectType) { SILInstruction::Bits.AllocRefInstBase.ObjC = objc; SILInstruction::Bits.AllocRefInstBase.OnStack = canBeOnStack; + SILInstruction::Bits.AllocRefInstBase.uniqueReference = isUnique; SILInstruction::Bits.AllocRefInstBase.NumTailTypes = ElementTypes.size(); assert(SILInstruction::Bits.AllocRefInstBase.NumTailTypes == ElementTypes.size() && "Truncation"); @@ -214,8 +214,8 @@ AllocRefInstBase::AllocRefInstBase(SILInstructionKind Kind, } AllocRefInst *AllocRefInst::create(SILDebugLocation Loc, SILFunction &F, - SILType ObjectType, - bool objc, bool canBeOnStack, + SILType ObjectType, bool objc, + bool canBeOnStack, bool isUnique, ArrayRef ElementTypes, ArrayRef ElementCountOperands, SILOpenedArchetypesState &OpenedArchetypes) { @@ -233,7 +233,7 @@ AllocRefInst *AllocRefInst::create(SILDebugLocation Loc, SILFunction &F, ElementTypes.size()); auto Buffer = F.getModule().allocateInst(Size, alignof(AllocRefInst)); return ::new (Buffer) AllocRefInst(Loc, F, ObjectType, objc, canBeOnStack, - ElementTypes, AllOperands); + isUnique, ElementTypes, AllOperands); } AllocRefDynamicInst * diff --git a/lib/SIL/IR/SILPrinter.cpp b/lib/SIL/IR/SILPrinter.cpp index 763c0122779b0..b44e5efd91525 100644 --- a/lib/SIL/IR/SILPrinter.cpp +++ b/lib/SIL/IR/SILPrinter.cpp @@ -1153,6 +1153,8 @@ class SILPrinter : public SILInstructionVisitor { *this << "[objc] "; if (ARI->canAllocOnStack()) *this << "[stack] "; + if (ARI->isUniqueReference()) + *this << "[unique] "; auto Types = ARI->getTailAllocatedTypes(); auto Counts = ARI->getTailAllocatedCounts(); for (unsigned Idx = 0, NumTypes = Types.size(); Idx < NumTypes; ++Idx) { diff --git a/lib/SIL/Parser/ParseSIL.cpp b/lib/SIL/Parser/ParseSIL.cpp index 8a9df9d667734..adfde00641748 100644 --- a/lib/SIL/Parser/ParseSIL.cpp +++ b/lib/SIL/Parser/ParseSIL.cpp @@ -3862,6 +3862,7 @@ bool SILParser::parseSpecificSILInstruction(SILBuilder &B, case SILInstructionKind::AllocRefDynamicInst: { bool IsObjC = false; bool OnStack = false; + bool isUnique = false; SmallVector ElementTypes; SmallVector ElementCounts; while (P.consumeIf(tok::l_square)) { @@ -3872,6 +3873,8 @@ bool SILParser::parseSpecificSILInstruction(SILBuilder &B, IsObjC = true; } else if (Optional == "stack") { OnStack = true; + } else if (Optional == "unique") { + isUnique = true; } else if (Optional == "tail_elems") { SILType ElemTy; if (parseSILType(ElemTy) || !P.Tok.isAnyOperator() || @@ -3916,7 +3919,7 @@ bool SILParser::parseSpecificSILInstruction(SILBuilder &B, ElementTypes, ElementCounts); } else { ResultVal = B.createAllocRef(InstLoc, ObjectType, IsObjC, OnStack, - ElementTypes, ElementCounts); + isUnique, ElementTypes, ElementCounts); } break; } diff --git a/lib/SIL/Utils/InstructionUtils.cpp b/lib/SIL/Utils/InstructionUtils.cpp index 9e8ffa340805c..a0bcd581f134b 100644 --- a/lib/SIL/Utils/InstructionUtils.cpp +++ b/lib/SIL/Utils/InstructionUtils.cpp @@ -653,3 +653,138 @@ swift::getStaticOverloadForSpecializedPolymorphicBuiltin(BuiltinInst *bi) { return newBI; } + +// Analyzing the body of this class destructor is valid because the object is +// dead. This means that the object is never passed to objc_setAssociatedObject, +// so its destructor cannot be extended at runtime. +SILFunction *swift::getDestructor(AllocRefInstBase *ARI) { + // We only support classes. + ClassDecl *ClsDecl = ARI->getType().getClassOrBoundGenericClass(); + if (!ClsDecl) + return nullptr; + + // Look up the destructor of ClsDecl. + DestructorDecl *Destructor = ClsDecl->getDestructor(); + assert(Destructor && "getDestructor() should never return a nullptr."); + + // Find the destructor name via SILDeclRef. + // FIXME: When destructors get moved into vtables, update this to use the + // vtable for the class. + SILDeclRef Ref(Destructor); + SILFunction *Fn = ARI->getModule().lookUpFunction(Ref); + if (!Fn || Fn->empty()) { + LLVM_DEBUG(llvm::dbgs() << " Could not find destructor.\n"); + return nullptr; + } + + LLVM_DEBUG(llvm::dbgs() << " Found destructor!\n"); + + // If the destructor has an objc_method calling convention, we cannot + // analyze it since it could be swapped out from under us at runtime. + if (Fn->getRepresentation() == SILFunctionTypeRepresentation::ObjCMethod) { + LLVM_DEBUG(llvm::dbgs() << " Found Objective-C destructor. Can't " + "analyze!\n"); + return nullptr; + } + + return Fn; +} + +Optional swift::isReferenceUnique(AllocRefInstBase *ref, + bool verbose) { + SmallPtrSet seenUses; + SmallVector usesToCheck; + usesToCheck.append(ref->use_begin(), ref->use_end()); + + while (!usesToCheck.empty()) { + auto *use = usesToCheck.pop_back_val(); + auto *user = use->getUser(); + + // Keep track of the operands that we've already seen to prevent recursion. + // If we've already seen this operand, continue. + if (!seenUses.insert(use).second) + continue; + + if (auto br = dyn_cast(user)) { + unsigned i = 0; + for (auto arg : br->getArgs()) { + if (arg == use->get()) + break; + i++; + } + auto arg = br->getDestBB()->getArgument(i); + usesToCheck.append(arg->use_begin(), arg->use_end()); + continue; + } + + if (auto apply = FullApplySite::isa(user)) { + if (!apply.getReferencedFunctionOrNull()) { + if (verbose) { + llvm::dbgs() << "Could not find the referenced function of the " + "following full apply site: \n"; + apply.getInstruction()->print(llvm::dbgs()); + } + return {"Found unkown use of unique reference."}; + } + + auto referencedFn = apply.getReferencedFunctionOrNull(); + if (referencedFn->empty()) { + if (verbose) { + llvm::dbgs() << "Cannot see the body of the referenced function: \n"; + referencedFn->print(llvm::dbgs()); + } + return {"Found unkown use of unique reference."}; + } + + unsigned i = 0; + for (auto arg : apply.getArguments()) { + if (arg == use->get()) + break; + i++; + } + auto arg = referencedFn->getArgument(i); + usesToCheck.append(arg->use_begin(), arg->use_end()); + continue; + } + + if (isa(user)) { + auto *destructor = getDestructor(ref); + if (!destructor) + return {"Cannot find destructor for reference type while checking " + "strong_release does not escape the unique reference."}; + auto *selfInDestructor = destructor->begin()->getArgument(0); + usesToCheck.append(selfInDestructor->use_begin(), + selfInDestructor->use_end()); + continue; + } + + // These instructions are OK. + if (isa(user) || isa(user) || + isa(user) || isa(user) || + isa(user) || isa(user) || + isa(user) || isa(user)) + continue; + + if (auto *store = dyn_cast(user)) { + if (store->getDest() != use->get()) + return {"Store escapes address. Store instruction must store into " + "the unique reference."}; + continue; + } + + if (isa(user) || isa(user) || + isa(user) || isa(user)) { + auto *userVal = cast(user); + usesToCheck.append(userVal->use_begin(), userVal->use_end()); + continue; + } + + if (verbose) { + llvm::dbgs() << "The following instruction may escape the reference: \n"; + user->print(llvm::dbgs()); + } + return {"Found unkown use of unique reference."}; + } + + return None; +} diff --git a/lib/SIL/Verifier/SILVerifier.cpp b/lib/SIL/Verifier/SILVerifier.cpp index cca9546938961..8c5983c569eb4 100644 --- a/lib/SIL/Verifier/SILVerifier.cpp +++ b/lib/SIL/Verifier/SILVerifier.cpp @@ -1290,6 +1290,19 @@ class SILVerifier : public SILVerifierBase { require(Counts[Idx].get()->getType().is(), "count needs integer type"); } + + // Check that there is only one reference of unique types. It's important + // that this is always statcially verifiable so if there is a case where the + // reference does not escape but, the SILVerifier doesn't know about it, + // then the right thing to do is fail. + if (ARI->isUniqueReference()) { + Optional isNotUnique = + isReferenceUnique(ARI, /*verbose=*/true); + if (isNotUnique) { + llvm::dbgs() << isNotUnique.getValue(); + require(false, "Failing due to above error."); + } + } } void checkAllocRefInst(AllocRefInst *AI) { diff --git a/lib/SILGen/SILGenBuilder.cpp b/lib/SILGen/SILGenBuilder.cpp index bf0e64a90cc79..f746970fd5953 100644 --- a/lib/SILGen/SILGenBuilder.cpp +++ b/lib/SILGen/SILGenBuilder.cpp @@ -208,7 +208,7 @@ SILGenBuilder::createGuaranteedTransformingTerminatorArgument(SILType type) { ManagedValue SILGenBuilder::createAllocRef( SILLocation loc, SILType refType, bool objc, bool canAllocOnStack, - ArrayRef inputElementTypes, + bool isUnique, ArrayRef inputElementTypes, ArrayRef inputElementCountOperands) { llvm::SmallVector elementTypes(inputElementTypes.begin(), inputElementTypes.end()); @@ -217,8 +217,9 @@ ManagedValue SILGenBuilder::createAllocRef( std::back_inserter(elementCountOperands), [](ManagedValue mv) -> SILValue { return mv.getValue(); }); - AllocRefInst *i = createAllocRef(loc, refType, objc, canAllocOnStack, - elementTypes, elementCountOperands); + AllocRefInst *i = + createAllocRef(loc, refType, objc, canAllocOnStack, isUnique, + elementTypes, elementCountOperands); return SGF.emitManagedRValueWithCleanup(i); } diff --git a/lib/SILGen/SILGenBuilder.h b/lib/SILGen/SILGenBuilder.h index b5b6c3567d213..421120c24b158 100644 --- a/lib/SILGen/SILGenBuilder.h +++ b/lib/SILGen/SILGenBuilder.h @@ -143,7 +143,7 @@ class SILGenBuilder : public SILBuilder { using SILBuilder::createAllocRef; ManagedValue createAllocRef(SILLocation loc, SILType refType, bool objc, - bool canAllocOnStack, + bool canAllocOnStack, bool isUnique, ArrayRef elementTypes, ArrayRef elementCountOperands); using SILBuilder::createAllocRefDynamic; diff --git a/lib/SILGen/SILGenBuiltin.cpp b/lib/SILGen/SILGenBuiltin.cpp index 46b9f4abd5988..2ee7aa72effd0 100644 --- a/lib/SILGen/SILGenBuiltin.cpp +++ b/lib/SILGen/SILGenBuiltin.cpp @@ -1001,8 +1001,8 @@ static ManagedValue emitBuiltinAllocWithTailElems(SILGenFunction &SGF, assert(InstanceType == RefType.getASTType() && "substituted type does not match operand metatype"); (void) InstanceType; - return SGF.B.createAllocRef(loc, RefType, false, false, - ElemTypes, Counts); + return SGF.B.createAllocRef(loc, RefType, false, false, false, ElemTypes, + Counts); } else { return SGF.B.createAllocRefDynamic(loc, Metatype, RefType, false, ElemTypes, Counts); diff --git a/lib/SILGen/SILGenConstructor.cpp b/lib/SILGen/SILGenConstructor.cpp index e81be5a9cefcc..dff109767caca 100644 --- a/lib/SILGen/SILGenConstructor.cpp +++ b/lib/SILGen/SILGenConstructor.cpp @@ -601,7 +601,7 @@ void SILGenFunction::emitClassConstructorAllocator(ConstructorDecl *ctor) { // allocated is the type of the class that defines the designated // initializer. F.setIsExactSelfClass(IsExactSelfClass); - selfValue = B.createAllocRef(Loc, selfTy, useObjCAllocation, false, + selfValue = B.createAllocRef(Loc, selfTy, useObjCAllocation, false, false, ArrayRef(), ArrayRef()); } args.push_back(selfValue); diff --git a/lib/SILOptimizer/PassManager/PassPipeline.cpp b/lib/SILOptimizer/PassManager/PassPipeline.cpp index 3f586c5cc2734..350aa2256439c 100644 --- a/lib/SILOptimizer/PassManager/PassPipeline.cpp +++ b/lib/SILOptimizer/PassManager/PassPipeline.cpp @@ -355,6 +355,8 @@ void addFunctionPasses(SILPassPipelinePlan &P, // Do a round of CFG simplification, followed by peepholes, then // more CFG simplification. + P.addMarkReferenceUnique(); + // Jump threading can expose opportunity for SILCombine (enum -> is_enum_tag-> // cond_br). P.addJumpThreadSimplifyCFG(); @@ -506,6 +508,10 @@ static void addHighLevelModulePipeline(SILPassPipelinePlan &P) { P.addGlobalOpt(); P.addLetPropertiesOpt(); + + P.addAccessEnforcementOpts(); + P.addAccessEnforcementWMO(); + // P.addAccessMarkerElimination(); } static void addSerializePipeline(SILPassPipelinePlan &P) { diff --git a/lib/SILOptimizer/SILCombiner/SILCombinerMiscVisitors.cpp b/lib/SILOptimizer/SILCombiner/SILCombinerMiscVisitors.cpp index e476210f9d48c..487aeb30f4afe 100644 --- a/lib/SILOptimizer/SILCombiner/SILCombinerMiscVisitors.cpp +++ b/lib/SILOptimizer/SILCombiner/SILCombinerMiscVisitors.cpp @@ -1888,10 +1888,9 @@ visitAllocRefDynamicInst(AllocRefDynamicInst *ARDI) { if (!SILInstanceTy.getClassOrBoundGenericClass()) return nullptr; - NewInst = Builder.createAllocRef(ARDI->getLoc(), SILInstanceTy, - ARDI->isObjC(), false, - ARDI->getTailAllocatedTypes(), - getCounts(ARDI)); + NewInst = Builder.createAllocRef( + ARDI->getLoc(), SILInstanceTy, ARDI->isObjC(), false, false, + ARDI->getTailAllocatedTypes(), getCounts(ARDI)); } else if (isa(MDVal)) { @@ -1913,10 +1912,9 @@ visitAllocRefDynamicInst(AllocRefDynamicInst *ARDI) { auto SILInstanceTy = SILType::getPrimitiveObjectType(InstanceTy); if (!SILInstanceTy.getClassOrBoundGenericClass()) return nullptr; - NewInst = Builder.createAllocRef(ARDI->getLoc(), SILInstanceTy, - ARDI->isObjC(), false, - ARDI->getTailAllocatedTypes(), - getCounts(ARDI)); + NewInst = Builder.createAllocRef( + ARDI->getLoc(), SILInstanceTy, ARDI->isObjC(), false, false, + ARDI->getTailAllocatedTypes(), getCounts(ARDI)); } } if (NewInst && NewInst->getType() != ARDI->getType()) { diff --git a/lib/SILOptimizer/Transforms/AccessEnforcementOpts.cpp b/lib/SILOptimizer/Transforms/AccessEnforcementOpts.cpp index 8aadfb4ce1f93..5a95be4d49920 100644 --- a/lib/SILOptimizer/Transforms/AccessEnforcementOpts.cpp +++ b/lib/SILOptimizer/Transforms/AccessEnforcementOpts.cpp @@ -917,6 +917,25 @@ removeLocalNonNestedAccess(const AccessConflictAndMergeAnalysis::Result &result, return changed; } +static bool promoteAccessToUniquelyIdentified( + const AccessConflictAndMergeAnalysis::Result &result, + const FunctionAccessedStorage &functionAccess) { + if (functionAccess.hasUnidentifiedAccess()) + return false; + + bool changed = false; + for (auto &beginAccessAndInfo : result.accessMap) { + BeginAccessInst *beginAccess = beginAccessAndInfo.first; + const AccessInfo &info = beginAccessAndInfo.second; + // Accesses to unique storage may be marked [static]. + if (info.isUniquelyIdentified()) { + beginAccess->setEnforcement(SILAccessEnforcement::Static); + changed = true; + } + } + return changed; +} + // TODO: support multi-end access cases static EndAccessInst *getSingleEndAccess(BeginAccessInst *inst) { EndAccessInst *end = nullptr; @@ -1129,6 +1148,12 @@ struct AccessEnforcementOpts : public SILFunctionTransform { if (removeLocalNonNestedAccess(result, functionAccess)) invalidateAnalysis(SILAnalysis::InvalidationKind::Instructions); + // Use the updated AccessedStorageAnalysis to find any uniquely identified + // storage accesses. All these accesses can be marked as statically + // enforced. + if (promoteAccessToUniquelyIdentified(result, functionAccess)) + invalidateAnalysis(SILAnalysis::InvalidationKind::Instructions); + // Perform the access merging // The inital version of the optimization requires a postDomTree PostDominanceAnalysis *postDomAnalysis = diff --git a/lib/SILOptimizer/Transforms/CMakeLists.txt b/lib/SILOptimizer/Transforms/CMakeLists.txt index 932a2e264e3a5..9d1b29d2e7028 100644 --- a/lib/SILOptimizer/Transforms/CMakeLists.txt +++ b/lib/SILOptimizer/Transforms/CMakeLists.txt @@ -20,6 +20,7 @@ target_sources(swiftSILOptimizer PRIVATE Devirtualizer.cpp DifferentiabilityWitnessDevirtualizer.cpp GenericSpecializer.cpp + MarkReferenceUnique.cpp MergeCondFail.cpp Outliner.cpp ObjectOutliner.cpp diff --git a/lib/SILOptimizer/Transforms/DeadObjectElimination.cpp b/lib/SILOptimizer/Transforms/DeadObjectElimination.cpp index fff5fafdde1f7..f5d27e66baa8d 100644 --- a/lib/SILOptimizer/Transforms/DeadObjectElimination.cpp +++ b/lib/SILOptimizer/Transforms/DeadObjectElimination.cpp @@ -61,42 +61,6 @@ STATISTIC(DeadAllocApplyEliminated, using UserList = llvm::SmallSetVector; -// Analyzing the body of this class destructor is valid because the object is -// dead. This means that the object is never passed to objc_setAssociatedObject, -// so its destructor cannot be extended at runtime. -static SILFunction *getDestructor(AllocRefInst *ARI) { - // We only support classes. - ClassDecl *ClsDecl = ARI->getType().getClassOrBoundGenericClass(); - if (!ClsDecl) - return nullptr; - - // Look up the destructor of ClsDecl. - DestructorDecl *Destructor = ClsDecl->getDestructor(); - assert(Destructor && "getDestructor() should never return a nullptr."); - - // Find the destructor name via SILDeclRef. - // FIXME: When destructors get moved into vtables, update this to use the - // vtable for the class. - SILDeclRef Ref(Destructor); - SILFunction *Fn = ARI->getModule().lookUpFunction(Ref); - if (!Fn || Fn->empty()) { - LLVM_DEBUG(llvm::dbgs() << " Could not find destructor.\n"); - return nullptr; - } - - LLVM_DEBUG(llvm::dbgs() << " Found destructor!\n"); - - // If the destructor has an objc_method calling convention, we cannot - // analyze it since it could be swapped out from under us at runtime. - if (Fn->getRepresentation() == SILFunctionTypeRepresentation::ObjCMethod) { - LLVM_DEBUG(llvm::dbgs() << " Found Objective-C destructor. Can't " - "analyze!\n"); - return nullptr; - } - - return Fn; -} - /// Analyze the destructor for the class of ARI to see if any instructions in it /// could have side effects on the program outside the destructor. If it does /// not, then we can eliminate the destructor. diff --git a/lib/SILOptimizer/Transforms/MarkReferenceUnique.cpp b/lib/SILOptimizer/Transforms/MarkReferenceUnique.cpp new file mode 100644 index 0000000000000..2721b331038b5 --- /dev/null +++ b/lib/SILOptimizer/Transforms/MarkReferenceUnique.cpp @@ -0,0 +1,63 @@ +//===---- MarkReferenceUnique.cpp - Mark references as unique -------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2019 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 +// +//===----------------------------------------------------------------------===// +//===----------------------------------------------------------------------===// + +#include "swift/Basic/LLVM.h" +#include "swift/SIL/InstructionUtils.h" +#include "swift/SIL/SILBuilder.h" +#include "swift/SILOptimizer/PassManager/Transforms.h" + +using namespace swift; + +namespace { + +//===----------------------------------------------------------------------===// +// MarkReferenceUnique +//===----------------------------------------------------------------------===// + +struct MarkReferenceUnique : public SILFunctionTransform { + MarkReferenceUnique() = default; + + /// The entry point to the transformation. + void run() override { + auto *func = getFunction(); + + bool madeChange = false; + for (auto &block : *func) { + for (auto &inst : block) { + if (auto *ref = dyn_cast(&inst)) { + // No point in doing exra work. + if (ref->isUniqueReference()) + continue; + + // The result of isReferenceUnique is an error message. If there's no + // message, then this is a unique reference. + bool isUnique = + isReferenceUnique(ref, /*verbose*/ false).hasValue() == false; + if (isUnique) { + madeChange = true; + ref->setUniqueReference(); + } + } + } + } + + if (madeChange) + invalidateAnalysis(SILAnalysis::InvalidationKind::Instructions); + } +}; + +} // end anonymous namespace + +SILTransform *swift::createMarkReferenceUnique() { + return new MarkReferenceUnique(); +} diff --git a/lib/Serialization/DeserializeSIL.cpp b/lib/Serialization/DeserializeSIL.cpp index 2ed57c9f0794b..867e251925c2a 100644 --- a/lib/Serialization/DeserializeSIL.cpp +++ b/lib/Serialization/DeserializeSIL.cpp @@ -1418,6 +1418,7 @@ bool SILDeserializer::readSILInstruction(SILFunction *Fn, unsigned Flags = ListOfValues[0]; bool isObjC = (bool)(Flags & 1); bool canAllocOnStack = (bool)((Flags >> 1) & 1); + bool isUnique = (bool)((Flags >> 2) & 1); SILType ClassTy = getSILType(MF->getType(TyID), (SILValueCategory)TyCategory, Fn); SmallVector Counts; @@ -1443,7 +1444,7 @@ bool SILDeserializer::readSILInstruction(SILFunction *Fn, } else { assert(i == NumVals); ResultVal = Builder.createAllocRef(Loc, ClassTy, isObjC, canAllocOnStack, - TailTypes, Counts); + isUnique, TailTypes, Counts); } break; } diff --git a/test/SIL/unique-reference-verifier-escape-with-apply.sil b/test/SIL/unique-reference-verifier-escape-with-apply.sil new file mode 100644 index 0000000000000..137024945c83f --- /dev/null +++ b/test/SIL/unique-reference-verifier-escape-with-apply.sil @@ -0,0 +1,32 @@ +// RUN: %empty-directory(%t) +// RUN: not --crash %target-sil-opt -enable-sil-verify-all %s 2> %t/err.txt +// RUN: %FileCheck %s < %t/err.txt + +sil_stage canonical + +import Builtin +import Swift +import SwiftShims + +public class MyClass { + @_hasStorage @_hasInitialValue var i: Int { get set } + deinit + init() +} + +sil @user : $@convention(thin) (MyClass) -> () + +// CHECK: The following instruction may escape the reference: +// CHECK: %{{[0-9]+}} = apply %{{[0-9]+}}(%{{[0-9]+}}) : $@convention(thin) (MyClass) -> () +// CHECK: SIL verification failed: Found unkown use of unique reference. +sil @simple : $@convention(thin) () -> () { +bb0: + %0 = alloc_ref [stack] [unique] $MyClass + %fn = function_ref @user : $@convention(thin) (MyClass) -> () + apply %fn(%0) : $@convention(thin) (MyClass) -> () + set_deallocating %0 : $MyClass + dealloc_ref %0 : $MyClass + dealloc_ref [stack] %0 : $MyClass + %14 = tuple () + return %14 : $() +} diff --git a/test/SIL/unique-reference-verifier-escape-with-store.sil b/test/SIL/unique-reference-verifier-escape-with-store.sil new file mode 100644 index 0000000000000..a1c5f3fabf800 --- /dev/null +++ b/test/SIL/unique-reference-verifier-escape-with-store.sil @@ -0,0 +1,29 @@ +// RUN: %empty-directory(%t) +// RUN: not --crash %target-sil-opt -enable-sil-verify-all %s 2> %t/err.txt +// RUN: %FileCheck %s < %t/err.txt + +sil_stage canonical + +import Builtin +import Swift +import SwiftShims + +public class MyClass { + @_hasStorage @_hasInitialValue var i: Int { get set } + deinit + init() +} + +// CHECK: SIL verification failed: Store escapes address. Store instruction must store into the unique reference.: store->getDest() == use->get() +sil @simple : $@convention(thin) () -> () { +bb0: + %arg = alloc_stack $MyClass + %0 = alloc_ref [stack] [unique] $MyClass + store %0 to %arg : $*MyClass + set_deallocating %0 : $MyClass + dealloc_ref %0 : $MyClass + dealloc_ref [stack] %0 : $MyClass + dealloc_stack %arg : $*MyClass + %14 = tuple () + return %14 : $() +} diff --git a/test/SIL/unique-reference-verifier-escape-with-strong-release.sil b/test/SIL/unique-reference-verifier-escape-with-strong-release.sil new file mode 100644 index 0000000000000..97743edc286c5 --- /dev/null +++ b/test/SIL/unique-reference-verifier-escape-with-strong-release.sil @@ -0,0 +1,31 @@ +// RUN: %empty-directory(%t) +// RUN: not --crash %target-sil-opt -enable-sil-verify-all %s 2> %t/err.txt +// RUN: %FileCheck %s < %t/err.txt + +sil_stage canonical + +import Builtin +import Swift +import SwiftShims + +public class MyClass { + @_hasStorage @_hasInitialValue var i: Int { get set } + deinit + init() +} + +// CHECK: The following instruction may escape the reference: +// CHECK: strong_release %{{[0-9]+}} : $MyClass +// CHECK: SIL verification failed: Found unkown use of unique reference. +sil @simple : $@convention(thin) () -> () { +bb0: + %arg = alloc_stack $MyClass + %0 = alloc_ref [stack] [unique] $MyClass + strong_release %0 : $MyClass + set_deallocating %0 : $MyClass + dealloc_ref %0 : $MyClass + dealloc_ref [stack] %0 : $MyClass + dealloc_stack %arg : $*MyClass + %14 = tuple () + return %14 : $() +} diff --git a/test/SIL/unique-reference-verifier-escape-with-unkown.sil b/test/SIL/unique-reference-verifier-escape-with-unkown.sil new file mode 100644 index 0000000000000..b166a11dba5e1 --- /dev/null +++ b/test/SIL/unique-reference-verifier-escape-with-unkown.sil @@ -0,0 +1,36 @@ +// RUN: %empty-directory(%t) +// RUN: not --crash %target-sil-opt -enable-sil-verify-all %s 2> %t/err.txt +// RUN: %FileCheck %s < %t/err.txt + +sil_stage canonical + +import Builtin +import Swift +import SwiftShims + +public class MyClass { + @_hasStorage @_hasInitialValue var i: Int { get set } + deinit + init() +} + +public class MyOtherClass { + @_hasStorage @_hasInitialValue var i: Int { get set } + deinit + init() +} + +// CHECK: The following instruction may escape the reference: +// CHECK: %{{[0-9]+}} = unchecked_ref_cast %{{[0-9]+}} : $MyClass to $MyOtherClass +// CHECK: SIL verification failed: Found unkown use of unique reference. +sil @simple : $@convention(thin) () -> () { +bb0: + %0 = alloc_ref [stack] [unique] $MyClass + // An "unkown" instruction. + %1 = unchecked_ref_cast %0 : $MyClass to $MyOtherClass + set_deallocating %0 : $MyClass + dealloc_ref %0 : $MyClass + dealloc_ref [stack] %0 : $MyClass + %14 = tuple () + return %14 : $() +} diff --git a/test/SIL/unique-reference-verifier.sil b/test/SIL/unique-reference-verifier.sil new file mode 100644 index 0000000000000..a4895138aa6bb --- /dev/null +++ b/test/SIL/unique-reference-verifier.sil @@ -0,0 +1,209 @@ +// RUN: %swift %s -emit-sil -module-name test | %FileCheck %s + +// The main thing here is to make sure that we can read in an `alloc_ref` with +// the `[unique]` flag and print it back out without triggering SILVerifier +// errors. + +sil_stage canonical + +import Builtin +import Swift +import SwiftShims + +public class MyClass { + @_hasStorage @_hasInitialValue var i: Int { get set } + deinit + init() +} + +// CHECK-LABEL: sil @simple +// CHECK: alloc_ref [stack] [unique] $MyClass +// CHECK-LABEL: end sil function 'simple' +sil @simple : $@convention(thin) () -> () { +bb0: + %0 = alloc_ref [stack] [unique] $MyClass + %2 = integer_literal $Builtin.Int64, 0 + %3 = struct $Int (%2 : $Builtin.Int64) + %4 = ref_element_addr %0 : $MyClass, #MyClass.i + %5 = begin_access [modify] [dynamic] [no_nested_conflict] %4 : $*Int + store %3 to %5 : $*Int + end_access %5 : $*Int + set_deallocating %0 : $MyClass + dealloc_ref %0 : $MyClass + dealloc_ref [stack] %0 : $MyClass + %14 = tuple () + return %14 : $() +} + +// CHECK-LABEL: sil @store_then_load +// CHECK: alloc_ref [stack] [unique] $MyClass +// CHECK-LABEL: end sil function 'store_then_load' +sil @store_then_load : $@convention(thin) () -> Int { +bb0: + %0 = alloc_ref [unique] [stack] $MyClass + debug_value %0 : $MyClass, let, name "self", argno 1 + %2 = integer_literal $Builtin.Int64, 0 + %3 = struct $Int (%2 : $Builtin.Int64) + %4 = ref_element_addr %0 : $MyClass, #MyClass.i + %5 = begin_access [modify] [dynamic] [no_nested_conflict] %4 : $*Int + store %3 to %5 : $*Int + end_access %5 : $*Int + debug_value %0 : $MyClass, let, name "x" + debug_value %0 : $MyClass, let, name "self", argno 1 + %10 = begin_access [read] [static] [no_nested_conflict] %4 : $*Int + %11 = load %10 : $*Int + end_access %10 : $*Int + set_deallocating %0 : $MyClass + debug_value %0 : $MyClass, let, name "self", argno 1 + debug_value %0 : $MyClass, let, name "self", argno 1 + dealloc_ref %0 : $MyClass + dealloc_ref [stack] %0 : $MyClass + return %11 : $Int +} + +// CHECK-LABEL: sil @multi_block +// CHECK: alloc_ref [stack] [unique] $MyClass +// CHECK-LABEL: end sil function 'multi_block' +sil @multi_block : $@convention(thin) (Bool) -> Int { +bb0(%0 : $Bool): + %2 = alloc_ref [unique] [stack] $MyClass + debug_value %2 : $MyClass, let, name "self", argno 1 + %4 = integer_literal $Builtin.Int64, 0 + %5 = struct $Int (%4 : $Builtin.Int64) + %6 = ref_element_addr %2 : $MyClass, #MyClass.i + %7 = begin_access [modify] [dynamic] [no_nested_conflict] %6 : $*Int + store %5 to %7 : $*Int + end_access %7 : $*Int + %10 = struct_extract %0 : $Bool, #Bool._value + cond_br %10, bb1, bb2 + +bb1: + %13 = begin_access [read] [static] [no_nested_conflict] %6 : $*Int + %14 = load %13 : $*Int + end_access %13 : $*Int + br bb3(%14 : $Int) + +bb2: + br bb3(%5 : $Int) + +bb3(%20 : $Int): + set_deallocating %2 : $MyClass + dealloc_ref %2 : $MyClass + dealloc_ref [stack] %2 : $MyClass + return %20 : $Int +} + +// CHECK-LABEL: sil @test_multiblock_with_branch +// CHECK: alloc_ref [stack] [unique] $MyClass +// CHECK-LABEL: end sil function 'test_multiblock_with_branch' +sil @test_multiblock_with_branch : $@convention(thin) (Bool, Bool) -> Int { +bb0(%0 : $Bool, %1 : $Bool): + %4 = alloc_ref [stack] [unique] $Foo + %6 = integer_literal $Builtin.Int64, 0 + %7 = struct $Int (%6 : $Builtin.Int64) + %8 = ref_element_addr %4 : $Foo, #Foo.x + %9 = begin_access [modify] [dynamic] [no_nested_conflict] %8 : $*Int + store %7 to %9 : $*Int + end_access %9 : $*Int + %12 = struct_extract %0 : $Bool, #Bool._value + cond_br %12, bb2, bb1 + +bb1: + br bb3 + +bb2: + %15 = integer_literal $Builtin.Int64, 1 + %16 = struct $Int (%15 : $Builtin.Int64) + %19 = begin_access [modify] [static] [no_nested_conflict] %8 : $*Int + store %16 to %19 : $*Int + end_access %19 : $*Int + br bb3 + +bb3: + %23 = struct_extract %1 : $Bool, #Bool._value + cond_br %23, bb4, bb5 + +bb4: + %25 = alloc_ref $Foo + %27 = ref_element_addr %25 : $Foo, #Foo.x + %28 = begin_access [modify] [dynamic] [no_nested_conflict] %27 : $*Int + store %7 to %28 : $*Int + end_access %28 : $*Int + // strong_release %4 : $Foo + br bb6(%25 : $Foo) + +bb5: + br bb6(%4 : $Foo) + +bb6(%34 : $Foo): + %36 = ref_element_addr %34 : $Foo, #Foo.x + %37 = begin_access [read] [dynamic] [no_nested_conflict] %36 : $*Int + %38 = load %37 : $*Int + end_access %37 : $*Int + // strong_release %34 : $Foo + dealloc_ref [stack] %4 : $Foo + return %38 : $Int +} + +sil [noinline] @user : $@convention(thin) (@guaranteed Foo) -> Int { +bb0(%0 : $Foo): + %2 = ref_element_addr %0 : $Foo, #Foo.x + %3 = begin_access [read] [dynamic] [no_nested_conflict] %2 : $*Int + %4 = load %3 : $*Int + end_access %3 : $*Int + return %4 : $Int +} + +// CHECK-LABEL: sil @test_apply +// CHECK: alloc_ref [stack] [unique] $MyClass +// CHECK-LABEL: end sil function 'test_apply' +sil @test_apply : $@convention(thin) (Bool, Bool) -> Int { +bb0(%0 : $Bool, %1 : $Bool): + %2 = alloc_ref [unique] $Foo + %4 = integer_literal $Builtin.Int64, 0 + %5 = struct $Int (%4 : $Builtin.Int64) + %6 = ref_element_addr %2 : $Foo, #Foo.x + %7 = begin_access [modify] [dynamic] [no_nested_conflict] %6 : $*Int + store %5 to %7 : $*Int + end_access %7 : $*Int + // function_ref user(_:) + %10 = function_ref @user : $@convention(thin) (@guaranteed Foo) -> Int + %11 = apply %10(%2) : $@convention(thin) (@guaranteed Foo) -> Int + // strong_release %2 : $Foo + return %11 : $Int +} + +// Foo.deinit +sil @$s4test3FooCfd : $@convention(method) (@guaranteed Foo) -> @owned Builtin.NativeObject { +// %0 "self" // users: %2, %1 +bb0(%0 : $Foo): + debug_value %0 : $Foo, let, name "self", argno 1 // id: %1 + %2 = unchecked_ref_cast %0 : $Foo to $Builtin.NativeObject // user: %3 + return %2 : $Builtin.NativeObject // id: %3 +} // end sil function '$s4test3FooCfd' + +// Foo.__deallocating_deinit +sil @$s4test3FooCfD : $@convention(method) (@owned Foo) -> () { +// %0 "self" // users: %3, %2, %1 +bb0(%0 : $Foo): + debug_value %0 : $Foo, let, name "self", argno 1 // id: %1 + debug_value %0 : $Foo, let, name "self", argno 1 // id: %2 + dealloc_ref %0 : $Foo // id: %3 + %4 = tuple () // user: %5 + return %4 : $() // id: %5 +} // end sil function '$s4test3FooCfD' + +// CHECK-LABEL: sil @test_strong_release +// CHECK: alloc_ref [stack] [unique] $MyClass +// CHECK-LABEL: end sil function 'test_strong_release' +sil @test_strong_release : $@convention(thin) (Bool, Bool) -> () { +bb0(%0 : $Bool, %1 : $Bool): + %2 = alloc_ref [unique] $Foo + strong_release %2 : $Foo + %t = tuple () + return %t : $() +} + +sil_vtable Foo { + #Foo.deinit!deallocator: @$s4test3FooCfD // Foo.__deallocating_deinit +} diff --git a/test/SILOptimizer/access_enforcement_opts.sil b/test/SILOptimizer/access_enforcement_opts.sil index 5f644b6a3411d..3593ab285bf2c 100644 --- a/test/SILOptimizer/access_enforcement_opts.sil +++ b/test/SILOptimizer/access_enforcement_opts.sil @@ -1737,3 +1737,31 @@ bb0(%0 : ${ var Int64 }): end_access %access : $*Int64 return %val : $Int64 } + +// CHECK-LABEL: sil @promote_unique_class_to_static +// CHECK: begin_access [modify] [static] [no_nested_conflict] +// CHECK: begin_access [read] [static] [no_nested_conflict] +// CHECK-LABEL: end sil function 'promote_unique_class_to_static' +sil @promote_unique_class_to_static : $@convention(thin) () -> () { +bb0: + %0 = integer_literal $Builtin.Int32, 0 + %1 = struct $Int32 (%0 : $Builtin.Int32) + %4 = alloc_ref [stack] [unique] $RefElemClass + + %8 = ref_element_addr %4 : $RefElemClass, #RefElemClass.y + %9 = begin_access [modify] [dynamic] [no_nested_conflict] %8 : $*Int32 + store %1 to %9 : $*Int32 + end_access %9 : $*Int32 + + %17 = begin_access [read] [dynamic] [no_nested_conflict] %8 : $*Int32 + %18 = struct_element_addr %17 : $*Int32, #Int32._value + %19 = load %18 : $*Builtin.Int32 + end_access %17 : $*Int32 + + set_deallocating %4 : $RefElemClass + dealloc_ref %4 : $RefElemClass + dealloc_ref [stack] %4 : $RefElemClass + + %9999 = tuple () + return %9999 : $() +}