Skip to content

Commit 6c3fd0e

Browse files
committed
Fix projecting a tuple element out of a tuple with packs.
At the same time, make sure we propagate contextual initializations. I'm actually not sure this is meant to be supported, but it's fine, we should implement it. Fixes #80937
1 parent bee053f commit 6c3fd0e

File tree

2 files changed

+110
-1
lines changed

2 files changed

+110
-1
lines changed

lib/SILGen/SILGenExpr.cpp

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2822,12 +2822,78 @@ RValue RValueEmitter::visitDynamicSubscriptExpr(
28222822
E->getType()->getCanonicalType(), C);
28232823
}
28242824

2825+
namespace {
2826+
class CollectValueInitialization : public Initialization {
2827+
ManagedValue Value;
2828+
2829+
public:
2830+
ManagedValue getValue() const {
2831+
assert(Value);
2832+
return Value;
2833+
}
2834+
2835+
void copyOrInitValueInto(SILGenFunction &SGF, SILLocation loc,
2836+
ManagedValue value, bool isInit) override {
2837+
if (!isInit) value = value.copy(SGF, loc);
2838+
Value = value;
2839+
}
2840+
};
2841+
}
28252842

28262843
RValue RValueEmitter::visitTupleElementExpr(TupleElementExpr *E,
28272844
SGFContext C) {
28282845
assert(!E->getType()->is<LValueType>() &&
28292846
"RValueEmitter shouldn't be called on lvalues");
2830-
2847+
2848+
auto tupleType = cast<TupleType>(E->getBase()->getType()->getCanonicalType());
2849+
auto projectedEltInit = C.getEmitInto();
2850+
2851+
std::optional<CollectValueInitialization> collection;
2852+
2853+
// If we have an initialization to emit into, or if we'd need to emit
2854+
// a tuple that contains pack expansions, make a tuple initialization
2855+
// of it plus some black holes.
2856+
if (projectedEltInit || tupleType->containsPackExpansionType()) {
2857+
TupleInitialization tupleInit(tupleType);
2858+
2859+
auto projectedIndex = E->getFieldNumber();
2860+
auto numElts = tupleType->getNumElements();
2861+
assert(projectedIndex < numElts);
2862+
2863+
tupleInit.SubInitializations.reserve(numElts);
2864+
for (auto i : range(numElts)) {
2865+
// Create a black-hole initialization for everything except the
2866+
// projected index.
2867+
if (i != projectedIndex) {
2868+
tupleInit.SubInitializations.emplace_back(new BlackHoleInitialization());
2869+
2870+
// If we have an initialization for the projected initialization,
2871+
// put it in the right place.
2872+
} else if (projectedEltInit) {
2873+
tupleInit.SubInitializations.emplace_back(projectedEltInit,
2874+
PointerIsNotOwned);
2875+
2876+
// Otherwise, create the collection initialization and put it in the
2877+
// right place.
2878+
} else {
2879+
collection.emplace();
2880+
tupleInit.SubInitializations.emplace_back(&*collection,
2881+
PointerIsNotOwned);
2882+
}
2883+
}
2884+
2885+
// Emit the expression into the initialization.
2886+
SGF.emitExprInto(E->getBase(), &tupleInit);
2887+
2888+
// If we had an initialization to emit into, we've done so.
2889+
if (projectedEltInit) {
2890+
return RValue::forInContext();
2891+
}
2892+
2893+
// Otherwise, pull out the owned value.
2894+
return RValue(SGF, E, collection->getValue());
2895+
}
2896+
28312897
// If our client is ok with a +0 result, then we can compute our base as +0
28322898
// and return its element that way. It would not be ok to reuse the Context's
28332899
// address buffer though, since our base value will a different type than the
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// RUN: %target-swift-emit-silgen -module-name test %s | %FileCheck %s
2+
3+
func returnStringAnyPair() -> (String, Any) {
4+
return ("hello", "world")
5+
}
6+
7+
// Test that we handle projections appropriately in emit-into contexts.
8+
// CHECK-LABEL: sil hidden [ossa] @$s4test0A17ProjectIntoReturnypyF :
9+
// CHECK: bb0(%0 : $*Any):
10+
// CHECK-NEXT: // function_ref
11+
// CHECK-NEXT: [[FN:%.*]] = function_ref @$s4test19returnStringAnyPairSS_yptyF :
12+
// CHECK-NEXT: [[STRING:%.*]] = apply [[FN]](%0)
13+
// CHECK-NEXT: ignored_use [[STRING]]
14+
// CHECK-NEXT: destroy_value [[STRING]]
15+
// CHECK-NEXT: [[RET:%.*]] = tuple ()
16+
// CHECK-NEXT: return [[RET]]
17+
func testProjectIntoReturn() -> Any {
18+
return returnStringAnyPair().1
19+
}
20+
21+
func returnAsTuple<each T>(_ args: repeat each T) -> (repeat each T) {
22+
(repeat each args)
23+
}
24+
25+
// Test that we handle projections of non-pack elements of tuples.
26+
// CHECK-LABEL: sil hidden [ossa] @$s4test0A14PackProjectionySSxxQpRvzlF :
27+
// CHECK: bb0(%0 : $*Pack{repeat each T}):
28+
// CHECK: [[VALUES:%.*]] = alloc_stack [lexical] [var_decl] $(String, repeat each T, String)
29+
// The result gets expanded, so we emit a pack loop to set up a pack
30+
// initialization of the elements of values. Eventually we do this call:
31+
// CHECK: [[FN:%.*]] = function_ref @$s4test13returnAsTupleyxxQp_txxQpRvzlF :
32+
// CHECK-NEXT: apply [[FN]]<Pack{String, repeat each T, String}>(
33+
// This copy is unnecessary.
34+
// CHECK: [[TEMP:%.*]] = alloc_stack $(String, repeat each T, String)
35+
// CHECK-NEXT: copy_addr [[VALUES]] to [init] [[TEMP]]
36+
// CHECK: [[INDEX2:%.*]] = scalar_pack_index 2 of
37+
// CHECK-NEXT: [[ELT2_ADDR:%.*]] = tuple_pack_element_addr [[INDEX2]] of [[TEMP]] as $*String
38+
// CHECK-NEXT: [[VALUE:%.*]] = load [take] [[ELT2_ADDR]]
39+
// CHECK: return [[VALUE]]
40+
func testPackProjection<each T>(_ args: repeat each T) -> String {
41+
let values = returnAsTuple("start", repeat each args, "end")
42+
return values.2
43+
}

0 commit comments

Comments
 (0)