From 09a738723a9696eb265e2614f40b2dfdffc76f9c Mon Sep 17 00:00:00 2001 From: zoecarver Date: Fri, 3 Apr 2020 11:52:15 -0700 Subject: [PATCH 1/5] [opt] Initialize stack alloc_ref with tuple. If possible, create a tuple that holds the stored properties of a class and replace as many uses of the class as possible with the tuple. Then store the tuple elements into the class properties. This allows both LLVM and the SIL optimizer to produce better codegen. --- .../Transforms/StackPromotion.cpp | 245 +++++++++++++++++- 1 file changed, 241 insertions(+), 4 deletions(-) diff --git a/lib/SILOptimizer/Transforms/StackPromotion.cpp b/lib/SILOptimizer/Transforms/StackPromotion.cpp index a7579edc39094..32c0275c0ab8c 100644 --- a/lib/SILOptimizer/Transforms/StackPromotion.cpp +++ b/lib/SILOptimizer/Transforms/StackPromotion.cpp @@ -12,6 +12,7 @@ #include "swift/SIL/BasicBlockUtils.h" #include "swift/SIL/CFG.h" +#include "swift/SIL/DebugUtils.h" #include "swift/SIL/SILArgument.h" #include "swift/SIL/SILBuilder.h" #include "swift/SILOptimizer/Analysis/EscapeAnalysis.h" @@ -51,6 +52,15 @@ class StackPromotion : public SILFunctionTransform { /// Tries to promote the allocation \p ARI. bool tryPromoteAlloc(AllocRefInst *ARI, EscapeAnalysis *EA, DeadEndBlocks &DEBlocks); + + /// Tries to promote the allocation \p ARI to an object. This optimization + /// will only happen if the class type has a compiler-generated constructor + /// and destructor. The promotion happens by scanning all uses in dominance + /// order. If all members are accounted for by ref_element_addr instruction + /// before we find any other use, then we can use those values to promote this + /// alloc_ref to an object. + bool tryPromoteToObject(AllocRefInst *allocRef, + ValueLifetimeAnalysis::Frontier &frontier); }; void StackPromotion::run() { @@ -84,10 +94,11 @@ void StackPromotion::run() { bool StackPromotion::promoteInBlock(SILBasicBlock *BB, EscapeAnalysis *EA, DeadEndBlocks &DEBlocks) { bool Changed = false; - for (auto Iter = BB->begin(); Iter != BB->end();) { - // The allocation instruction may be moved, so increment Iter prior to - // doing the optimization. - SILInstruction *I = &*Iter++; + SmallVector allInstructions; + for (SILInstruction &inst : *BB) { + allInstructions.push_back(&inst); + } + for (auto *I : allInstructions) { if (auto *ARI = dyn_cast(I)) { // Don't stack promote any allocation inside a code region which ends up // in a no-return block. Such allocations may missing their final release. @@ -142,6 +153,9 @@ bool StackPromotion::tryPromoteAlloc(AllocRefInst *ARI, EscapeAnalysis *EA, } NumStackPromoted++; + if (tryPromoteToObject(ARI, Frontier)) + return true; + // We set the [stack] attribute in the alloc_ref. ARI->setStackAllocatable(); @@ -153,6 +167,229 @@ bool StackPromotion::tryPromoteAlloc(AllocRefInst *ARI, EscapeAnalysis *EA, return true; } +static void getOrderedNonDebugUses(SILValue v, DominanceInfo *domInfo, + SmallVectorImpl &uses) { + auto unsorted = getNonDebugUses(v); + uses.append(unsorted.begin(), unsorted.end()); + llvm::sort(uses, [&domInfo](Operand *a, Operand *b) { + return domInfo->dominates(a->getUser(), b->getUser()); + }); +} + +/// Gets the correct store ownership qualifier to initialize an address based on +/// the value. If the function has no ownership, \returns unqualified. If the +/// value is trivial, \returns trival. Otherwise, \returns init. +static StoreOwnershipQualifier storeInitQualifier(SILValue v) { + if (v->getFunction()->hasOwnership()) + return v->getType().isTrivial(*v->getFunction()) + ? StoreOwnershipQualifier::Trivial + : StoreOwnershipQualifier::Init; + return StoreOwnershipQualifier::Unqualified; +} + +static void copyTupleToRef(SILValue src, AllocRefInst *refDest, + SILBuilder &builder) { + auto loc = src.getLoc(); + auto classDecl = refDest->getType().getClassOrBoundGenericClass(); + auto tupleSrc = dyn_cast(src); + // Handle if there is only one propery, the source could be any type. + if (!tupleSrc) { + assert(classDecl->getStoredProperties().size() == 1 && + "If the source is not a tuple, there must only be a single stored " + "property."); + auto onlyProp = classDecl->getStoredProperties().front(); + auto elementDest = builder.createRefElementAddr(loc, refDest, onlyProp); + builder.createStore(loc, src, elementDest, storeInitQualifier(src)); + return; + } + // Otherwise, copy every tuple element into the class. + unsigned i = 0; + for (auto *prop : classDecl->getStoredProperties()) { + auto elementDest = builder.createRefElementAddr(loc, refDest, prop); + auto elementSrc = builder.createTupleExtract(loc, tupleSrc, i++); + builder.createStore(loc, elementSrc, elementDest, + storeInitQualifier(elementSrc)); + } +} + +bool StackPromotion::tryPromoteToObject( + AllocRefInst *allocRef, ValueLifetimeAnalysis::Frontier &frontier) { + DominanceInfo *domInfo = + PM->getAnalysis()->get(allocRef->getFunction()); + auto *classDecl = allocRef->getType().getClassOrBoundGenericClass(); + if (!classDecl || !classDecl->getDestructor()->isImplicit() || + (classDecl->getAsGenericContext() && + classDecl->getAsGenericContext()->isGeneric())) + return false; + + SmallVector props; + for (auto *prop : classDecl->getStoredProperties()) { + props.push_back(prop); + } + + SmallVector uses; + getOrderedNonDebugUses(allocRef, domInfo, uses); + + std::reverse(props.begin(), props.end()); + SmallVector propertyInitializers; + for (auto *use : uses) { + auto propRef = dyn_cast(use->getUser()); + if (!propRef) + return false; + + auto f = props.pop_back_val(); + if (propRef->getField() != f) + return false; + + propertyInitializers.push_back(propRef); + + if (props.empty()) + break; + } + + if (!props.empty()) + return false; + + SmallVector deadStores; + SmallVector elements; + for (auto *init : propertyInitializers) { + SmallVector refElementUses; + getOrderedNonDebugUses(init, domInfo, refElementUses); + auto frontUser = refElementUses.front()->getUser(); + if (auto *beginAccess = dyn_cast(frontUser)) { + SmallVector beginAccessUses; + getOrderedNonDebugUses(beginAccess, domInfo, beginAccessUses); + frontUser = beginAccessUses.front()->getUser(); + } + if (auto *store = dyn_cast(frontUser)) { + elements.push_back(store->getSrc()); + deadStores.push_back(store); + } else { + return false; + } + } + + SILInstruction *lastElement = nullptr; + for (auto first = elements.rbegin(); first != elements.rend(); ++first) { + auto inst = first->getDefiningInstruction(); + if (!inst) + continue; + + if (!lastElement || domInfo->dominates(lastElement, inst)) + lastElement = inst; + } + + // If we didn't find anything, that means that all the elements are arguments, + // or there aren't any elements. Either way, we know that putting where the + // alloc_ref is will work. + if (!lastElement || domInfo->dominates(lastElement, allocRef)) + lastElement = allocRef; + + // Create a tuple to store the properties of the class. The tuple must go + // after all element instructions. + SILBuilder builder(std::next(lastElement->getIterator())); + SILValue storedProps; + if (elements.size() == 1) + storedProps = elements[0]; + else + storedProps = builder.createTuple(lastElement->getLoc(), elements); + auto storedPropsAddr = + builder.createAllocStack(lastElement->getLoc(), storedProps->getType()); + auto refInitStore = + builder.createStore(lastElement->getLoc(), storedProps, storedPropsAddr, + storeInitQualifier(storedProps)); + + // Find the first use of the alloc_ref that isn't a ref_element_addr and + // record where that element is. + SmallVector users(allocRef->use_begin(), allocRef->use_end()); + SILInstruction *firstUnknownUse = nullptr; + for (auto *use : users) { + auto user = use->getUser(); + + if (isa(user) || isa(user) || + isa(user) || isa(user) || + isa(user)) + continue; + + firstUnknownUse = user; + break; + } + + // Keep track of the last instruction we've added so we know where to put the + // final copy. + SILInstruction *endUse = nullptr; + for (auto *use : users) { + auto user = use->getUser(); + if (firstUnknownUse && domInfo->dominates(firstUnknownUse, user)) + continue; + + if (auto ref = dyn_cast(user)) { + if (ref->use_empty()) { + ref->eraseFromParent(); + continue; + } + + for (auto *use : ref->getUses()) { + if (firstUnknownUse && + domInfo->dominates(firstUnknownUse, use->getUser())) + continue; + if (!endUse || domInfo->dominates(endUse, use->getUser())) + endUse = use->getUser(); + } + + if (isa(storedProps)) { + auto index = std::distance( + classDecl->getStoredProperties().begin(), + llvm::find(classDecl->getStoredProperties(), ref->getField())); + builder.setInsertionPoint(std::next(refInitStore->getIterator())); + auto tupleElementAddr = builder.createTupleElementAddr( + ref->getLoc(), storedPropsAddr, index); + ref->replaceAllUsesWith(tupleElementAddr); + } else { + ref->replaceAllUsesWith(storedPropsAddr); + } + ref->eraseFromParent(); + } + } + + // Create the copy_to_ref after the last instruction we've added. If none are + // added put it directly after the tuple instruction. With LLVM optimization + // enabled, this will still be a performance win in most (all?) cases. + if (endUse) { + builder.setInsertionPoint(std::next(endUse->getIterator())); + } else { + builder.setInsertionPoint(std::next(refInitStore->getIterator())); + } + if (firstUnknownUse) { + // TODO: This needs to happen after the last _use_ of _any_ of the tuple + // instructions. builder.setInsertionPoint(firstUnknownUse); + copyTupleToRef(storedProps, allocRef, builder); + } + + for (SILInstruction *frontierInst : frontier) { + SILBuilder deallocBuilder(frontierInst); + deallocBuilder.createDestroyAddr(storedPropsAddr->getLoc(), + storedPropsAddr); + deallocBuilder.createDeallocStack(storedPropsAddr->getLoc(), + storedPropsAddr); + } + + for (auto *store : deadStores) { + store->eraseFromParent(); + } + + llvm::errs() << "Promoted to object in stack. Function: " + << storedProps->getFunction()->getName() << "\n"; + + if (!firstUnknownUse) { + eraseUsesOfValue(allocRef); + allocRef->eraseFromParent(); + return true; + } + + return false; +} + } // end anonymous namespace SILTransform *swift::createStackPromotion() { From 3cea7d1fb421615330df515abbdb6ccb93145b46 Mon Sep 17 00:00:00 2001 From: zoecarver Date: Sat, 4 Apr 2020 11:55:44 -0700 Subject: [PATCH 2/5] Cleanup & comment tryPromoteToObject --- .../Transforms/StackPromotion.cpp | 60 ++++++++++++------- 1 file changed, 39 insertions(+), 21 deletions(-) diff --git a/lib/SILOptimizer/Transforms/StackPromotion.cpp b/lib/SILOptimizer/Transforms/StackPromotion.cpp index 32c0275c0ab8c..cd725e64152df 100644 --- a/lib/SILOptimizer/Transforms/StackPromotion.cpp +++ b/lib/SILOptimizer/Transforms/StackPromotion.cpp @@ -217,11 +217,6 @@ bool StackPromotion::tryPromoteToObject( DominanceInfo *domInfo = PM->getAnalysis()->get(allocRef->getFunction()); auto *classDecl = allocRef->getType().getClassOrBoundGenericClass(); - if (!classDecl || !classDecl->getDestructor()->isImplicit() || - (classDecl->getAsGenericContext() && - classDecl->getAsGenericContext()->isGeneric())) - return false; - SmallVector props; for (auto *prop : classDecl->getStoredProperties()) { props.push_back(prop); @@ -229,10 +224,12 @@ bool StackPromotion::tryPromoteToObject( SmallVector uses; getOrderedNonDebugUses(allocRef, domInfo, uses); - + // Reverse the properties so we can pop_back a property that matches the next + // initializer. std::reverse(props.begin(), props.end()); SmallVector propertyInitializers; for (auto *use : uses) { + // Assume properties are initialized in order. auto propRef = dyn_cast(use->getUser()); if (!propRef) return false; @@ -246,21 +243,24 @@ bool StackPromotion::tryPromoteToObject( if (props.empty()) break; } - + // Bail if we haven't found all the properties. if (!props.empty()) return false; + // Collect the dead stores and values of the class class property initializers. SmallVector deadStores; SmallVector elements; for (auto *init : propertyInitializers) { SmallVector refElementUses; getOrderedNonDebugUses(init, domInfo, refElementUses); auto frontUser = refElementUses.front()->getUser(); + // Look through begin_access uses. if (auto *beginAccess = dyn_cast(frontUser)) { SmallVector beginAccessUses; getOrderedNonDebugUses(beginAccess, domInfo, beginAccessUses); frontUser = beginAccessUses.front()->getUser(); } + // If the first use isn't a store, bail. if (auto *store = dyn_cast(frontUser)) { elements.push_back(store->getSrc()); deadStores.push_back(store); @@ -269,6 +269,7 @@ bool StackPromotion::tryPromoteToObject( } } + // Keep track of the last element so we know where to insert the tuple. SILInstruction *lastElement = nullptr; for (auto first = elements.rbegin(); first != elements.rend(); ++first) { auto inst = first->getDefiningInstruction(); @@ -289,10 +290,15 @@ bool StackPromotion::tryPromoteToObject( // after all element instructions. SILBuilder builder(std::next(lastElement->getIterator())); SILValue storedProps; + // If there is only one element, we can't make a tuple so, just use that + // element. if (elements.size() == 1) storedProps = elements[0]; else + // Otherwise, use a tuple to hold the stored properties. storedProps = builder.createTuple(lastElement->getLoc(), elements); + // Make an alloc_stack so that we can replace ref_element_addr instructions + // with tuple_element_addr instructions. auto storedPropsAddr = builder.createAllocStack(lastElement->getLoc(), storedProps->getType()); auto refInitStore = @@ -300,7 +306,8 @@ bool StackPromotion::tryPromoteToObject( storeInitQualifier(storedProps)); // Find the first use of the alloc_ref that isn't a ref_element_addr and - // record where that element is. + // record where that use is. We use that as an upper bound for were we have to + // stop replacing uses of the class reference. SmallVector users(allocRef->use_begin(), allocRef->use_end()); SILInstruction *firstUnknownUse = nullptr; for (auto *use : users) { @@ -324,19 +331,25 @@ bool StackPromotion::tryPromoteToObject( continue; if (auto ref = dyn_cast(user)) { + // If this instruction is dead, remove it and continue. We do this so that + // we can be sure endUse will be correct. if (ref->use_empty()) { ref->eraseFromParent(); continue; } + // Make sure all uses come before the first unknown use. for (auto *use : ref->getUses()) { if (firstUnknownUse && domInfo->dominates(firstUnknownUse, use->getUser())) continue; + // Then update endUse so that we put the final copy in the right place. if (!endUse || domInfo->dominates(endUse, use->getUser())) endUse = use->getUser(); } + // Either replace ref with a tuple_element_addr or the alloc_stack if + // there's only one element. if (isa(storedProps)) { auto index = std::distance( classDecl->getStoredProperties().begin(), @@ -352,20 +365,22 @@ bool StackPromotion::tryPromoteToObject( } } - // Create the copy_to_ref after the last instruction we've added. If none are - // added put it directly after the tuple instruction. With LLVM optimization - // enabled, this will still be a performance win in most (all?) cases. - if (endUse) { - builder.setInsertionPoint(std::next(endUse->getIterator())); - } else { - builder.setInsertionPoint(std::next(refInitStore->getIterator())); - } + // If there aren't any unknown uses, we will remove the alloc_ref so don't + // bother copying the storage into it. if (firstUnknownUse) { - // TODO: This needs to happen after the last _use_ of _any_ of the tuple - // instructions. builder.setInsertionPoint(firstUnknownUse); + // Copy the property storage to the alloc_ref after the last instruction + // we've added. If none are added put it directly after the tuple + // instruction. With LLVM optimization enabled, this will still be a + // performance win in most (all?) cases. + if (endUse) { + builder.setInsertionPoint(std::next(endUse->getIterator())); + } else { + builder.setInsertionPoint(std::next(refInitStore->getIterator())); + } copyTupleToRef(storedProps, allocRef, builder); } + // Make sure we destroy/dealloc the property storage. for (SILInstruction *frontierInst : frontier) { SILBuilder deallocBuilder(frontierInst); deallocBuilder.createDestroyAddr(storedPropsAddr->getLoc(), @@ -374,19 +389,22 @@ bool StackPromotion::tryPromoteToObject( storedPropsAddr); } + // Cleanup the stores that are now dead. for (auto *store : deadStores) { store->eraseFromParent(); } - llvm::errs() << "Promoted to object in stack. Function: " - << storedProps->getFunction()->getName() << "\n"; - + // If we don't have any unknown uses there's no reason for the class so still + // exist so, remove it. if (!firstUnknownUse) { eraseUsesOfValue(allocRef); allocRef->eraseFromParent(); + // Return true so that we don't try to promote this alloc_ref to stack. return true; } + // We were succesful in optimizing this alloc_ref but return false so that + // StackPromotion will still try to promote this alloc_ref to stack. return false; } From ce0270e24d6f74b3b59cba81ad204bc9113ad206 Mon Sep 17 00:00:00 2001 From: zoecarver Date: Sat, 4 Apr 2020 12:22:20 -0700 Subject: [PATCH 3/5] Add stack promotion test to test that alloc_refs are removed and users are replaced --- test/SILOptimizer/stack_promotion.swift | 86 +++++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 test/SILOptimizer/stack_promotion.swift diff --git a/test/SILOptimizer/stack_promotion.swift b/test/SILOptimizer/stack_promotion.swift new file mode 100644 index 0000000000000..0fa60d930e659 --- /dev/null +++ b/test/SILOptimizer/stack_promotion.swift @@ -0,0 +1,86 @@ +// RUN: %target-swift-frontend %s -O -emit-sil | %FileCheck %s + +class Foo { + var x : Int = 0 +} + + func a( _ f : Foo) { + f.x += 1 +} + + func b( _ f : Foo) -> Int { + return f.x + 2 +} + +// CHECK-LABEL: sil_global private @$s15stack_promotion8arr_testSiyFTv_ +// CHECK-NOT: alloc_ref +// CHECK-NOT: ref_element_addr +// CHECK: %initval = object + +// CHECK-LABEL: sil @$s15stack_promotion14simple_foo_useySiSbF +// CHECK-NOT: alloc_ref +// CHECK-NOT: ref_element_addr +// CHECK: [[I:%.*]] = integer_literal +// CHECK-NEXT: [[X:%.*]] = struct $Int ([[I]] +// CHECK-NEXT: [[SA:%.*]] = alloc_stack $Int +// CHECK-NEXT: store [[X]] to [[SA]] : $*Int +// CHECK-NEXT: [[BA:%.*]] = begin_access [read] [dynamic] [no_nested_conflict] [[SA]] +// CHECK-NEXT: [[VAL:%.*]] = load [[BA]] +// CHECK-NEXT: end_access [[BA]] +// CHECK-NEXT: dealloc_stack [[SA]] +// CHECK-NEXT: return [[VAL]] +// CHECK-LABEL: end sil function '$s15stack_promotion14simple_foo_useySiSbF' +public func simple_foo_use(_ check : Bool) -> Int { + let f = Foo() + return f.x +} + +// CHECK-LABEL: sil @$s15stack_promotion3a_bySiSbF +// CHECK-NOT: alloc_ref +// CHECK-NOT: ref_element_addr +// CHECK: [[I:%.*]] = integer_literal +// CHECK-NEXT: [[X:%.*]] = struct $Int ([[I]] +// CHECK-NEXT: [[SA:%.*]] = alloc_stack $Int +// CHECK-NEXT: store [[X]] to [[SA]] : $*Int +// CHECK-LABEL: end sil function '$s15stack_promotion3a_bySiSbF' +public func a_b(_ check : Bool) -> Int { + let f = Foo() + if check { + a(f) + } + return b(f) +} + +class Bar { + var x : [Int] + init (_ x: [Int]) { + self.x = x + } +} + +// CHECK-LABEL: sil @$s15stack_promotion8arr_testSiyF +// CHECK-NOT: alloc_ref +// CHECK-NOT: ref_element_addr +// CHECK: global_value @$s15stack_promotion8arr_testSiyFTv_ : $_ContiguousArrayStorage +// CHECK-LABEL: end sil function '$s15stack_promotion8arr_testSiyF' +public func arr_test() -> Int { + let arr = [0, 1, 2, 3, 4] + let f = Bar(arr) + return f.x.count +} + +class Gen { + var x : T + init (_ x: T) { + self.x = x + } +} + +// CHECK-LABEL: sil @$s15stack_promotion12test_genericyyF +// CHECK: bb0 +// CHECK-NEXT: tuple +// CHECK-NEXT: return +// CHECK-LABEL: end sil function '$s15stack_promotion12test_genericyyF' +public func test_generic() { + _ = Gen(0) +} From 3886bd861b62d8193e5d4f566910b1358c59411e Mon Sep 17 00:00:00 2001 From: zoecarver Date: Sun, 5 Apr 2020 14:33:51 -0700 Subject: [PATCH 4/5] Fix issues exposed by benchmarks: * Ref element addrs that had uses outside "firstUnknonwUse" would still be replaced. * Some strong releases need to be removed when erasing the alloc_ref. --- benchmark/single-source/Prims.swift | 1 - lib/SILOptimizer/Transforms/StackPromotion.cpp | 18 +++++++++++++++--- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/benchmark/single-source/Prims.swift b/benchmark/single-source/Prims.swift index 3906a89d2bec1..97331b02c10fd 100644 --- a/benchmark/single-source/Prims.swift +++ b/benchmark/single-source/Prims.swift @@ -756,6 +756,5 @@ public func run_Prims(_ N: Int) { for i in 1.. deadStores; SmallVector elements; for (auto *init : propertyInitializers) { @@ -308,7 +309,8 @@ bool StackPromotion::tryPromoteToObject( // Find the first use of the alloc_ref that isn't a ref_element_addr and // record where that use is. We use that as an upper bound for were we have to // stop replacing uses of the class reference. - SmallVector users(allocRef->use_begin(), allocRef->use_end()); + SmallVector users; + getOrderedNonDebugUses(allocRef, domInfo, users); SILInstruction *firstUnknownUse = nullptr; for (auto *use : users) { auto user = use->getUser(); @@ -327,6 +329,14 @@ bool StackPromotion::tryPromoteToObject( SILInstruction *endUse = nullptr; for (auto *use : users) { auto user = use->getUser(); + + // Either we'll remove the alloc ref in which case this needs to be removed, + // or we'll add a dealloc_ref so we also need this to be removed. + if (isa(user)) { + user->eraseFromParent(); + continue; + } + if (firstUnknownUse && domInfo->dominates(firstUnknownUse, user)) continue; @@ -342,7 +352,7 @@ bool StackPromotion::tryPromoteToObject( for (auto *use : ref->getUses()) { if (firstUnknownUse && domInfo->dominates(firstUnknownUse, use->getUser())) - continue; + goto done; // Then update endUse so that we put the final copy in the right place. if (!endUse || domInfo->dominates(endUse, use->getUser())) endUse = use->getUser(); @@ -363,6 +373,8 @@ bool StackPromotion::tryPromoteToObject( } ref->eraseFromParent(); } + done: + continue; } // If there aren't any unknown uses, we will remove the alloc_ref so don't From 5ca96d97c8d6ba523a3a72b76c37514ef557467d Mon Sep 17 00:00:00 2001 From: zoecarver Date: Mon, 6 Apr 2020 11:26:04 -0700 Subject: [PATCH 5/5] Skip alloc_ref with tail elements It seems like this may be causing some regressions without gains in any cases so, skipping those alloc_refs is benefitial. --- lib/SILOptimizer/Transforms/StackPromotion.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/SILOptimizer/Transforms/StackPromotion.cpp b/lib/SILOptimizer/Transforms/StackPromotion.cpp index b0efc40c4504b..26d17d0cbca2d 100644 --- a/lib/SILOptimizer/Transforms/StackPromotion.cpp +++ b/lib/SILOptimizer/Transforms/StackPromotion.cpp @@ -214,6 +214,9 @@ static void copyTupleToRef(SILValue src, AllocRefInst *refDest, bool StackPromotion::tryPromoteToObject( AllocRefInst *allocRef, ValueLifetimeAnalysis::Frontier &frontier) { + if (allocRef->getTailAllocatedCounts().size()) + return false; + DominanceInfo *domInfo = PM->getAnalysis()->get(allocRef->getFunction()); auto *classDecl = allocRef->getType().getClassOrBoundGenericClass();