Skip to content

Commit a7a9e16

Browse files
committed
SILGen: Open code calls of enum case constructors
Sema models enum case constructors as ApplyExprs. Formerly SILGen would emit a case constructor function for each enum case, constructing the enum value in the constructor body. ApplyExprs of case constructors were lowered like any other call. This is nice and straightforward but has several downsides: 1) Case constructor functions are very repetitive and trivial, in particular for no-payload cases. They were declared @_transparent and so were inlined at call sites, but for public enums they still had to be emitted into the final object file. 2) If the enum is generic, the substituted type may be loadable even if the unsubstituted type is not, but since the case constructor is polymorphic we had to allocate stack buffers anyway, to pass the payload and result at the right abstration level. This meant that for example Optional.Some(foo) generated less-efficient SIL than the equivalent implicit conversion. 3) We were missing out on peephole optimization opportunities when the payload of an indirect case or address-only enum could be emitted directly into the destination buffer, avoiding a copy. One example would be when an enum payload is the result of calling a function that returns an address-only value indirectly. It appears we had unnecessary copies and takes even with -O. Again, optional implicit conversions special-cased this. This patch implements a new approach where a fully-formed call to a element constructor is handled via a special code path where the 'enum' or 'init_enum_data_addr' / 'inject_enum_addr' instructions are emitted directly. These always work on the substituted type, avoiding stack allocations unless needed. An additional optimization is that the ArgumentSource abstraction is used to delay evaluation of the payload argument until the indirect box or address-only payload was set up. If a element constructor is partially applied, we still emit a reference to the constant as before. It may seem like case constructor functions are at least useful for resilience, but case constructors are transparent, so making them resilient would require a new "transparent but only in this module, and don't serialize the SIL body" declaration. @inline(always) is almost what we need here, but this affect mandatory inlining, only the optimizer, so it would be a regression for non-resilient enums, or usages of resilient enums in the current module. A better approach is to construct resilient enums with a new destructiveInjectEnumTag value witness function, which is coming soon, and the general improvement from that approach is what prompted this patch.
1 parent 7496730 commit a7a9e16

File tree

8 files changed

+370
-138
lines changed

8 files changed

+370
-138
lines changed

lib/SILGen/SILGenApply.cpp

Lines changed: 79 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -230,9 +230,13 @@ class Callee {
230230
enum class Kind {
231231
/// An indirect function value.
232232
IndirectValue,
233+
233234
/// A direct standalone function call, referenceable by a FunctionRefInst.
234235
StandaloneFunction,
235236

237+
/// Enum case constructor call.
238+
EnumElement,
239+
236240
VirtualMethod_First,
237241
/// A method call using class method dispatch.
238242
ClassMethod = VirtualMethod_First,
@@ -462,6 +466,13 @@ class Callee {
462466
SILLocation l) {
463467
return Callee(gen, c, substFormalType, l);
464468
}
469+
static Callee forEnumElement(SILGenFunction &gen, SILDeclRef c,
470+
CanAnyFunctionType substFormalType,
471+
SILLocation l) {
472+
assert(isa<EnumElementDecl>(c.getDecl()));
473+
return Callee(Kind::EnumElement, gen, SILValue(),
474+
c, substFormalType, l);
475+
}
465476
static Callee forClassMethod(SILGenFunction &gen, SILValue selfValue,
466477
SILDeclRef name,
467478
CanAnyFunctionType substFormalType,
@@ -530,6 +541,7 @@ class Callee {
530541
return 0;
531542

532543
case Kind::StandaloneFunction:
544+
case Kind::EnumElement:
533545
case Kind::ClassMethod:
534546
case Kind::SuperMethod:
535547
case Kind::WitnessMethod:
@@ -538,6 +550,11 @@ class Callee {
538550
}
539551
}
540552

553+
EnumElementDecl *getEnumElementDecl() {
554+
assert(kind == Kind::EnumElement);
555+
return cast<EnumElementDecl>(Constant.getDecl());
556+
}
557+
541558
std::tuple<ManagedValue, CanSILFunctionType,
542559
Optional<ForeignErrorConvention>, ApplyOptions>
543560
getAtUncurryLevel(SILGenFunction &gen, unsigned level) const {
@@ -570,6 +587,20 @@ class Callee {
570587
mv = ManagedValue::forUnmanaged(ref);
571588
break;
572589
}
590+
case Kind::EnumElement: {
591+
assert(level <= Constant.uncurryLevel
592+
&& "uncurrying past natural uncurry level of enum constructor");
593+
constant = Constant.atUncurryLevel(level);
594+
constantInfo = gen.getConstantInfo(*constant);
595+
596+
// We should not end up here if the enum constructor call is fully
597+
// applied.
598+
assert(constant->isCurried);
599+
600+
SILValue ref = gen.emitGlobalFunctionRef(Loc, *constant, constantInfo);
601+
mv = ManagedValue::forUnmanaged(ref);
602+
break;
603+
}
573604
case Kind::ClassMethod: {
574605
assert(level <= Constant.uncurryLevel
575606
&& "uncurrying past natural uncurry level of method");
@@ -706,6 +737,7 @@ class Callee {
706737
case Kind::SuperMethod: {
707738
return SpecializedEmitter(emitPartialSuperMethod);
708739
}
740+
case Kind::EnumElement:
709741
case Kind::IndirectValue:
710742
case Kind::ClassMethod:
711743
case Kind::WitnessMethod:
@@ -1246,8 +1278,12 @@ class SILGenApply : public Lowering::ExprVisitor<SILGenApply> {
12461278
subs = e->getDeclRef().getSubstitutions();
12471279
}
12481280

1249-
setCallee(Callee::forDirect(SGF, constant, substFnType, e));
1250-
1281+
// Enum case constructor references are open-coded.
1282+
if (isa<EnumElementDecl>(e->getDecl()))
1283+
setCallee(Callee::forEnumElement(SGF, constant, substFnType, e));
1284+
else
1285+
setCallee(Callee::forDirect(SGF, constant, substFnType, e));
1286+
12511287
// If there are substitutions, add them, always at depth 0.
12521288
if (!subs.empty())
12531289
ApplyCallee->setSubstitutions(SGF, e, subs, 0);
@@ -3320,6 +3356,11 @@ namespace {
33203356
uncurriedSites[0].convertToPlusOneFromPlusZero(gen);
33213357
}
33223358

3359+
/// Is this a fully-applied enum element constructor call?
3360+
bool isEnumElementConstructor() {
3361+
return (callee.kind == Callee::Kind::EnumElement && uncurries == 0);
3362+
}
3363+
33233364
/// Emit the fully-formed call.
33243365
ManagedValue apply(SGFContext C = SGFContext()) {
33253366
assert(!applied && "already applied!");
@@ -3352,6 +3393,16 @@ namespace {
33523393
origFormalType = AbstractionPattern(formalType);
33533394
substFnType = gen.getLoweredType(formalType, uncurryLevel)
33543395
.castTo<SILFunctionType>();
3396+
} else if (isEnumElementConstructor()) {
3397+
// Enum payloads are always stored at the abstraction level
3398+
// of the unsubstituted payload type. This means that unlike
3399+
// with specialized emitters above, enum constructors use
3400+
// the AST-level abstraction pattern, to ensure that function
3401+
// types in payloads are re-abstracted correctly.
3402+
assert(!AssumedPlusZeroSelf);
3403+
substFnType = gen.getLoweredType(origFormalType, formalType,
3404+
uncurryLevel)
3405+
.castTo<SILFunctionType>();
33553406
} else {
33563407
std::tie(mv, substFnType, foreignError, initialOptions) =
33573408
callee.getAtUncurryLevel(gen, uncurryLevel);
@@ -3399,6 +3450,32 @@ namespace {
33993450
argument,
34003451
formalApplyType,
34013452
uncurriedContext);
3453+
} else if (isEnumElementConstructor()) {
3454+
// If we have a fully-applied enum element constructor, open-code
3455+
// the construction.
3456+
EnumElementDecl *element = callee.getEnumElementDecl();
3457+
3458+
SILLocation uncurriedLoc = uncurriedSites[0].Loc;
3459+
3460+
// Ignore metatype argument
3461+
claimNextParamClause(origFormalType);
3462+
claimNextParamClause(formalType);
3463+
std::move(uncurriedSites[0]).forward().getAsSingleValue(gen);
3464+
3465+
// Get the payload argument.
3466+
ArgumentSource payload;
3467+
if (element->hasArgumentType()) {
3468+
assert(uncurriedSites.size() == 2);
3469+
claimNextParamClause(origFormalType);
3470+
claimNextParamClause(formalType);
3471+
payload = std::move(uncurriedSites[1]).forward();
3472+
} else {
3473+
assert(uncurriedSites.size() == 1);
3474+
}
3475+
3476+
result = gen.emitInjectEnum(uncurriedLoc, std::move(payload),
3477+
substFnType->getSemanticResultSILType(),
3478+
element, uncurriedContext);
34023479

34033480
// Otherwise, emit the uncurried arguments now and perform
34043481
// the call.

test/SILGen/enum.swift

Lines changed: 156 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,165 @@
11
// RUN: %target-swift-frontend -parse-as-library -parse-stdlib -emit-silgen %s | FileCheck %s
22

3+
enum Boolish {
4+
case falsy
5+
case truthy
6+
}
7+
8+
// CHECK-LABEL: sil hidden @_TF4enum13Boolish_casesFT_T_
9+
func Boolish_cases() {
10+
// CHECK: [[BOOLISH:%[0-9]+]] = metatype $@thin Boolish.Type
11+
// CHECK-NEXT: [[FALSY:%[0-9]+]] = enum $Boolish, #Boolish.falsy!enumelt
12+
_ = Boolish.falsy
13+
14+
// CHECK-NEXT: [[BOOLISH:%[0-9]+]] = metatype $@thin Boolish.Type
15+
// CHECK-NEXT: [[TRUTHY:%[0-9]+]] = enum $Boolish, #Boolish.truthy!enumelt
16+
_ = Boolish.truthy
17+
}
18+
19+
struct Int {}
20+
21+
enum Optionable {
22+
case nought
23+
case mere(Int)
24+
}
25+
26+
// CHECK-LABEL: sil hidden [transparent] @_TFO4enum10Optionable4merefMS0_FVS_3IntS0_
27+
// CHECK: bb0([[ARG:%.*]] : $Int, {{%.*}} : $@thin Optionable.Type):
28+
// CHECK-NEXT: [[RES:%.*]] = enum $Optionable, #Optionable.mere!enumelt.1, [[ARG]] : $Int
29+
// CHECK-NEXT: return [[RES]] : $Optionable
30+
// CHECK-NEXT: }
31+
32+
// CHECK-LABEL: sil hidden @_TF4enum16Optionable_casesFVS_3IntT_
33+
func Optionable_cases(x: Int) {
34+
35+
// CHECK: [[FN:%.*]] = function_ref @_TFO4enum10Optionable4mereFMS0_FVS_3IntS0_
36+
// CHECK-NEXT: [[METATYPE:%.*]] = metatype $@thin Optionable.Type
37+
// CHECK-NEXT: [[CTOR:%.*]] = apply [[FN]]([[METATYPE]])
38+
// CHECK-NEXT: strong_release [[CTOR]]
39+
_ = Optionable.mere
40+
41+
// CHECK-NEXT: [[METATYPE:%.*]] = metatype $@thin Optionable.Type
42+
// CHECK-NEXT: [[RES:%.*]] = enum $Optionable, #Optionable.mere!enumelt.1, %0 : $Int
43+
_ = Optionable.mere(x)
44+
}
45+
346
protocol P {}
47+
struct S : P {}
48+
49+
enum AddressOnly {
50+
case nought
51+
case mere(P)
52+
case phantom(S)
53+
}
54+
55+
// CHECK-LABEL: sil hidden [transparent] @_TFO4enum11AddressOnly4merefMS0_FPS_1P_S0_ : $@convention(thin) (@out AddressOnly, @in P, @thin AddressOnly.Type) -> () {
56+
// CHECK: bb0([[RET:%.*]] : $*AddressOnly, [[DATA:%.*]] : $*P, {{%.*}} : $@thin AddressOnly.Type):
57+
// CHECK-NEXT: [[RET_DATA:%.*]] = init_enum_data_addr [[RET]] : $*AddressOnly, #AddressOnly.mere!enumelt.1 // user: %4
58+
// CHECK-NEXT: copy_addr [take] [[DATA]] to [initialization] [[RET_DATA]] : $*P
59+
// CHECK-NEXT: inject_enum_addr [[RET]] : $*AddressOnly, #AddressOnly.mere!enumelt.1
60+
// CHECK: return
61+
// CHECK-NEXT: }
62+
63+
// CHECK-LABEL: sil hidden @_TF4enum17AddressOnly_casesFVS_1ST_ : $@convention(thin) (S) -> ()
64+
func AddressOnly_cases(s: S) {
65+
66+
// CHECK: [[FN:%.*]] = function_ref @_TFO4enum11AddressOnly4mereFMS0_FPS_1P_S0_
67+
// CHECK-NEXT: [[METATYPE:%.*]] = metatype $@thin AddressOnly.Type
68+
// CHECK-NEXT: [[CTOR:%.*]] = apply [[FN]]([[METATYPE]])
69+
// CHECK-NEXT: strong_release [[CTOR]]
70+
_ = AddressOnly.mere
71+
72+
// CHECK-NEXT: [[METATYPE:%.*]] = metatype $@thin AddressOnly.Type
73+
// CHECK-NEXT: [[NOUGHT:%.*]] = alloc_stack $AddressOnly
74+
// CHECK-NEXT: inject_enum_addr [[NOUGHT]]#1
75+
// CHECK-NEXT: destroy_addr [[NOUGHT]]#1
76+
// CHECK-NEXT: dealloc_stack [[NOUGHT]]#0
77+
_ = AddressOnly.nought
78+
79+
// CHECK-NEXT: [[METATYPE:%.*]] = metatype $@thin AddressOnly.Type
80+
// CHECK-NEXT: [[MERE:%.*]] = alloc_stack $AddressOnly
81+
// CHECK-NEXT: [[PAYLOAD:%.*]] = init_enum_data_addr [[MERE]]#1
82+
// CHECK-NEXT: [[PAYLOAD_ADDR:%.*]] = init_existential_addr [[PAYLOAD]]
83+
// CHECK-NEXT: store %0 to [[PAYLOAD_ADDR]]
84+
// CHECK-NEXT: inject_enum_addr [[MERE]]#1
85+
// CHECK-NEXT: destroy_addr [[MERE]]#1
86+
// CHECK-NEXT: dealloc_stack [[MERE]]#0
87+
_ = AddressOnly.mere(s)
88+
89+
// Address-only enum vs loadable payload
90+
91+
// CHECK-NEXT: [[METATYPE:%.*]] = metatype $@thin AddressOnly.Type
92+
// CHECK-NEXT: [[PHANTOM:%.*]] = alloc_stack $AddressOnly
93+
// CHECK-NEXT: [[PAYLOAD:%.*]] = init_enum_data_addr %20#1 : $*AddressOnly, #AddressOnly.phantom!enumelt.1
94+
// CHECK-NEXT: store %0 to [[PAYLOAD]]
95+
// CHECK-NEXT: inject_enum_addr [[PHANTOM]]#1 : $*AddressOnly, #AddressOnly.phantom!enumelt.1
96+
// CHECK-NEXT: destroy_addr [[PHANTOM]]#1
97+
// CHECK-NEXT: dealloc_stack [[PHANTOM]]#0
98+
99+
_ = AddressOnly.phantom(s)
100+
// CHECK: return
101+
}
102+
103+
enum PolyOptionable<T> {
104+
case nought
105+
case mere(T)
106+
}
107+
108+
// CHECK-LABEL: sil hidden @_TF4enum20PolyOptionable_casesurFxT_
109+
func PolyOptionable_cases<T>(t: T) {
110+
111+
// CHECK: [[METATYPE:%.*]] = metatype $@thin PolyOptionable<T>.Type
112+
// CHECK-NEXT: [[NOUGHT:%.*]] = alloc_stack $PolyOptionable<T>
113+
// CHECK-NEXT: inject_enum_addr [[NOUGHT]]#1
114+
// CHECK-NEXT: destroy_addr [[NOUGHT]]#1
115+
// CHECK-NEXT: dealloc_stack [[NOUGHT]]#0
116+
_ = PolyOptionable<T>.nought
117+
118+
// CHECK-NEXT: [[METATYPE:%.*]] = metatype $@thin PolyOptionable<T>.Type
119+
// CHECK-NEXT: [[MERE:%.*]] = alloc_stack $PolyOptionable<T>
120+
// CHECK-NEXT: [[PAYLOAD:%.*]] = init_enum_data_addr [[MERE]]#1
121+
// CHECK-NEXT: copy_addr %0 to [initialization] [[PAYLOAD]]
122+
// CHECK-NEXT: inject_enum_addr [[MERE]]#1
123+
// CHECK-NEXT: destroy_addr [[MERE]]#1
124+
// CHECK-NEXT: dealloc_stack [[MERE]]#0
125+
126+
_ = PolyOptionable<T>.mere(t)
127+
128+
// CHECK-NEXT: destroy_addr %0
129+
// CHECK: return
130+
131+
}
132+
133+
// The substituted type is loadable and trivial here
134+
135+
// CHECK-LABEL: sil hidden @_TF4enum32PolyOptionable_specialized_casesFVS_3IntT_
136+
func PolyOptionable_specialized_cases(t: Int) {
137+
138+
// CHECK: [[METATYPE:%.*]] = metatype $@thin PolyOptionable<Int>.Type
139+
// CHECK-NEXT: [[NOUGHT:%.*]] = enum $PolyOptionable<Int>, #PolyOptionable.nought!enumelt
140+
_ = PolyOptionable<Int>.nought
141+
142+
// CHECK-NEXT: [[METATYPE:%.*]] = metatype $@thin PolyOptionable<Int>.Type
143+
// CHECK-NEXT: [[NOUGHT:%.*]] = enum $PolyOptionable<Int>, #PolyOptionable.mere!enumelt.1, %0
144+
_ = PolyOptionable<Int>.mere(t)
145+
146+
// CHECK: return
147+
148+
}
4149

5-
struct String { var ptr: Builtin.NativeObject }
6150

7151
// Regression test for a bug where temporary allocations created as a result of
8152
// tuple implosion were not deallocated in enum constructors.
153+
struct String { var ptr: Builtin.NativeObject }
154+
9155
enum Foo { case A(P, String) }
10156

11-
// CHECK: sil hidden [transparent] @_TFO4enum3Foo1AfMS0_FTPS_1P_VS_6String_S0_ : $@convention(thin) (@out Foo, @in P, @owned String, @thin Foo.Type) -> () {
12-
// CHECK: [[ALLOC:%.*]] = alloc_stack $(P, String)
13-
// CHECK: dealloc_stack [[ALLOC]]#0
14-
// CHECK: }
157+
// CHECK-LABEL: sil hidden [transparent] @_TFO4enum3Foo1AfMS0_FTPS_1P_VS_6String_S0_ : $@convention(thin) (@out Foo, @in P, @owned String, @thin Foo.Type) -> () {
158+
// CHECK: [[PAYLOAD:%.*]] = init_enum_data_addr %0 : $*Foo, #Foo.A!enumelt.1
159+
// CHECK-NEXT: [[LEFT:%.*]] = tuple_element_addr [[PAYLOAD]] : $*(P, String), 0
160+
// CHECK-NEXT: [[RIGHT:%.*]] = tuple_element_addr [[PAYLOAD]] : $*(P, String), 1
161+
// CHECK-NEXT: copy_addr [take] %1 to [initialization] [[LEFT]] : $*P
162+
// CHECK-NEXT: store %2 to [[RIGHT]]
163+
// CHECK-NEXT: inject_enum_addr %0 : $*Foo, #Foo.A!enumelt.1
164+
// CHECK: return
165+
// CHECK-NEXT: }

test/SILGen/errors.swift

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,9 @@ func make_a_cat() throws -> Cat {
2121

2222
// CHECK: sil hidden @_TF6errors15dont_make_a_cat{{.*}} : $@convention(thin) () -> (@owned Cat, @error ErrorType) {
2323
// CHECK: [[BOX:%.*]] = alloc_existential_box $ErrorType, $HomeworkError
24-
// CHECK: [[T0:%.*]] = function_ref @_TFO6errors13HomeworkError7TooHardFMS0_S0_ : $@convention(thin) (@thin HomeworkError.Type) -> @owned HomeworkError
25-
// CHECK-NEXT: [[T1:%.*]] = metatype $@thin HomeworkError.Type
26-
// CHECK-NEXT: [[T2:%.*]] = apply [[T0]]([[T1]])
27-
// CHECK-NEXT: store [[T2]] to [[BOX]]#1
24+
// CHECK-NEXT: [[T0:%.*]] = metatype $@thin HomeworkError.Type
25+
// CHECK-NEXT: [[T1:%.*]] = enum $HomeworkError, #HomeworkError.TooHard!enumelt
26+
// CHECK-NEXT: store [[T1]] to [[BOX]]#1
2827
// CHECK-NEXT: builtin "willThrow"
2928
// CHECK-NEXT: throw [[BOX]]#0
3029
func dont_make_a_cat() throws -> Cat {
@@ -33,10 +32,9 @@ func dont_make_a_cat() throws -> Cat {
3332

3433
// CHECK: sil hidden @_TF6errors11dont_return{{.*}} : $@convention(thin) <T> (@out T, @in T) -> @error ErrorType {
3534
// CHECK: [[BOX:%.*]] = alloc_existential_box $ErrorType, $HomeworkError
36-
// CHECK: [[T0:%.*]] = function_ref @_TFO6errors13HomeworkError7TooMuchFMS0_S0_ : $@convention(thin) (@thin HomeworkError.Type) -> @owned HomeworkError
37-
// CHECK-NEXT: [[T1:%.*]] = metatype $@thin HomeworkError.Type
38-
// CHECK-NEXT: [[T2:%.*]] = apply [[T0]]([[T1]])
39-
// CHECK-NEXT: store [[T2]] to [[BOX]]#1
35+
// CHECK-NEXT: [[T0:%.*]] = metatype $@thin HomeworkError.Type
36+
// CHECK-NEXT: [[T1:%.*]] = enum $HomeworkError, #HomeworkError.TooMuch!enumelt
37+
// CHECK-NEXT: store [[T1]] to [[BOX]]#1
4038
// CHECK-NEXT: builtin "willThrow"
4139
// CHECK-NEXT: destroy_addr %1 : $*T
4240
// CHECK-NEXT: throw [[BOX]]#0

0 commit comments

Comments
 (0)