Skip to content

Commit 54993b7

Browse files
authored
Merge pull request #61332 from hamishknight/write-that-down
2 parents e1d1760 + 8bc80c1 commit 54993b7

File tree

6 files changed

+216
-3
lines changed

6 files changed

+216
-3
lines changed

include/swift/SIL/SILDeclRef.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -556,6 +556,12 @@ struct SILDeclRef {
556556

557557
bool isImplicit() const;
558558

559+
/// Whether the referenced function contains user code. This is generally true
560+
/// for a non-implicit decls, but may also be true for implicit decls if
561+
/// explicitly written code has been spliced into the body. This is the case
562+
/// for e.g a lazy variable getter.
563+
bool hasUserWrittenCode() const;
564+
559565
/// Return the scope in which the parent class of a method (i.e. class
560566
/// containing this declaration) can be subclassed, returning NotApplicable if
561567
/// this is not a method, there is no such class, or the class cannot be

lib/SIL/IR/SILDeclRef.cpp

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,74 @@ bool SILDeclRef::isImplicit() const {
284284
llvm_unreachable("Unhandled case in switch");
285285
}
286286

287+
bool SILDeclRef::hasUserWrittenCode() const {
288+
// Non-implicit decls generally have user-written code.
289+
if (!isImplicit()) {
290+
switch (kind) {
291+
case Kind::PropertyWrapperBackingInitializer: {
292+
// Only has user-written code if any of the property wrappers have
293+
// arguments to apply. Otherwise, it's just a forwarding initializer for
294+
// the wrappedValue.
295+
auto *var = cast<VarDecl>(getDecl());
296+
return llvm::any_of(var->getAttachedPropertyWrappers(), [&](auto *attr) {
297+
return attr->hasArgs();
298+
});
299+
}
300+
case Kind::PropertyWrapperInitFromProjectedValue:
301+
// Never has user-written code, is just a forwarding initializer.
302+
return false;
303+
default:
304+
// TODO: This checking is currently conservative, we ought to
305+
// exhaustively handle all the cases here, and use emitOrDelayFunction
306+
// in more cases to take advantage of it.
307+
return true;
308+
}
309+
llvm_unreachable("Unhandled case in switch!");
310+
}
311+
312+
// Implicit decls generally don't have user-written code, but some splice
313+
// user code into their body.
314+
switch (kind) {
315+
case Kind::Func: {
316+
// Lazy getters splice in the user-written initializer expr.
317+
if (auto *accessor = dyn_cast<AccessorDecl>(getFuncDecl())) {
318+
auto *storage = accessor->getStorage();
319+
if (accessor->isGetter() && !storage->isImplicit() &&
320+
storage->getAttrs().hasAttribute<LazyAttr>()) {
321+
return true;
322+
}
323+
}
324+
return false;
325+
}
326+
case Kind::StoredPropertyInitializer: {
327+
// Property wrapper initializers for the implicit backing storage can splice
328+
// in the user-written initializer on the original property.
329+
auto *var = cast<VarDecl>(getDecl());
330+
if (auto *originalProperty = var->getOriginalWrappedProperty()) {
331+
if (originalProperty->isPropertyMemberwiseInitializedWithWrappedType())
332+
return true;
333+
}
334+
return false;
335+
}
336+
case Kind::Allocator:
337+
case Kind::Initializer:
338+
case Kind::EnumElement:
339+
case Kind::Destroyer:
340+
case Kind::Deallocator:
341+
case Kind::GlobalAccessor:
342+
case Kind::DefaultArgGenerator:
343+
case Kind::IVarInitializer:
344+
case Kind::IVarDestroyer:
345+
case Kind::PropertyWrapperBackingInitializer:
346+
case Kind::PropertyWrapperInitFromProjectedValue:
347+
case Kind::EntryPoint:
348+
case Kind::AsyncEntryPoint:
349+
// Implicit decls for these don't splice in user-written code.
350+
return false;
351+
}
352+
llvm_unreachable("Unhandled case in switch!");
353+
}
354+
287355
namespace {
288356
enum class LinkageLimit {
289357
/// No limit.

lib/SILGen/SILGen.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1186,7 +1186,7 @@ static void emitOrDelayFunction(SILGenModule &SGM,
11861186
// Implicit decls may be delayed if they can't be used externally.
11871187
auto linkage = constant.getLinkage(ForDefinition);
11881188
bool mayDelay = !forceEmission &&
1189-
(constant.isImplicit() &&
1189+
(!constant.hasUserWrittenCode() &&
11901190
!constant.isDynamicallyReplaceable() &&
11911191
!isPossiblyUsedExternally(linkage, SGM.M.isWholeModule()));
11921192

lib/Sema/TypeCheckPropertyWrapper.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -714,7 +714,9 @@ Expr *swift::buildPropertyWrapperInitCall(
714714
ApplyExpr *innermostInit = nullptr;
715715

716716
// Projected-value initializers don't compose, so no need to iterate
717-
// over the wrapper attributes.
717+
// over the wrapper attributes. NOTE: If this ever changes, you'll need to
718+
// update SILDeclRef::hasUserWrittenCode to account for any spliced in
719+
// user-written code.
718720
if (initKind == PropertyWrapperInitKind::ProjectedValue) {
719721
auto typeExpr = TypeExpr::createImplicit(backingStorageType, ctx);
720722
auto *argList = ArgumentList::forImplicitSingle(ctx, ctx.Id_projectedValue,

test/SILGen/delayed_functions.swift

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
// RUN: %target-swift-emit-silgen -primary-file %s | %FileCheck %s --check-prefix=SINGLE
2+
// RUN: %target-swift-emit-silgen %s | %FileCheck %s --check-prefix=WHOLEMOD
3+
// RUN: %target-swift-emit-sil -verify -primary-file %s
4+
5+
// https://github.com/apple/swift/issues/61128
6+
// Ensure we only delay emission of functions that do not contain user code.
7+
8+
// We cannot delay functions that contain user code.
9+
// SINGLE-LABEL: sil hidden [ossa] @$s17delayed_functions3fooSiyF : $@convention(thin) () -> Int
10+
// WHOLEMOD-LABEL: sil hidden [ossa] @$s17delayed_functions3fooSiyF : $@convention(thin) () -> Int
11+
func foo() -> Int { 5 }
12+
13+
// Cannot delay property intializers that contain user code.
14+
struct R {
15+
// variable initialization expression of R.i
16+
// SINGLE-LABEL: sil hidden [transparent] [ossa] @$s17delayed_functions1RV1iSivpfi : $@convention(thin) () -> Int
17+
// WHOLEMOD-LABEL: sil hidden [transparent] [ossa] @$s17delayed_functions1RV1iSivpfi : $@convention(thin) () -> Int
18+
var i = 0
19+
20+
// R.j.getter
21+
// SINGLE-LABEL: sil hidden [lazy_getter] [noinline] [ossa] @$s17delayed_functions1RV1jSivg : $@convention(method) (@inout R) -> Int
22+
// WHOLEMOD-LABEL: sil hidden [lazy_getter] [noinline] [ossa] @$s17delayed_functions1RV1jSivg : $@convention(method) (@inout R) -> Int
23+
lazy var j = Int.max + 1 // expected-error {{results in an overflow}}
24+
}
25+
26+
@propertyWrapper
27+
struct Wrapper<T> {
28+
var wrappedValue: T
29+
init(wrappedValue: T) {
30+
self.wrappedValue = wrappedValue
31+
}
32+
init(wrappedValue: T, _ x: Int) {
33+
self.wrappedValue = wrappedValue
34+
}
35+
}
36+
37+
struct Projected<T> {
38+
var value: T
39+
}
40+
@propertyWrapper
41+
struct ProjectedWrapper<T> {
42+
var wrappedValue: T
43+
init(wrappedValue: T) {
44+
self.wrappedValue = wrappedValue
45+
}
46+
var projectedValue: Projected<T> {
47+
.init(value: wrappedValue)
48+
}
49+
init(projectedValue: Projected<T>) {
50+
self.wrappedValue = projectedValue.value
51+
}
52+
}
53+
54+
struct S {
55+
// We can delay property wrapper backing initializers if they don't contain user code.
56+
// property wrapper backing initializer of S.i
57+
// SINGLE-LABEL: sil hidden [ossa] @$s17delayed_functions1SV1iSivpfP : $@convention(thin) (Int) -> Wrapper<Int>
58+
// WHOLEMOD-NOT: sil hidden [ossa] @$s17delayed_functions1SV1iSivpfP : $@convention(thin) (Int) -> Wrapper<Int>
59+
@Wrapper var i: Int
60+
61+
// But not if they contain user code.
62+
// property wrapper backing initializer of S.j
63+
// SINGLE-LABEL: sil hidden [ossa] @$s17delayed_functions1SV1jSSvpfP : $@convention(thin) (@owned String) -> @owned Wrapper<String>
64+
// WHOLEMOD-LABEL: sil hidden [ossa] @$s17delayed_functions1SV1jSSvpfP : $@convention(thin) (@owned String) -> @owned Wrapper<String>
65+
66+
// variable initialization expression of S._j
67+
// SINGLE-LABEL: sil hidden [transparent] [ossa] @$s17delayed_functions1SV2_j33_6D0D158845E4BE9792202A0D16664A88LLAA7WrapperVySSGvpfi : $@convention(thin) () -> @owned String
68+
// WHOLEMOD-LABEL: sil hidden [transparent] [ossa] @$s17delayed_functions1SV2_j33_6D0D158845E4BE9792202A0D16664A88LLAA7WrapperVySSGvpfi : $@convention(thin) () -> @owned String
69+
@Wrapper(5) var j = ""
70+
71+
// variable initialization expression of S._k
72+
// SINGLE-LABEL: sil hidden [transparent] [ossa] @$s17delayed_functions1SV2_k33_6D0D158845E4BE9792202A0D16664A88LLAA7WrapperVySiGvpfi : $@convention(thin) () -> Int
73+
// WHOLEMOD-LABEL: sil hidden [transparent] [ossa] @$s17delayed_functions1SV2_k33_6D0D158845E4BE9792202A0D16664A88LLAA7WrapperVySiGvpfi : $@convention(thin) () -> Int
74+
@Wrapper var k = Int.max + 1 // expected-error 2{{results in an overflow}}
75+
76+
// property wrapper backing initializer of S.l
77+
// SINGLE-LABEL: sil hidden [ossa] @$s17delayed_functions1SV1lSSvpfP : $@convention(thin) (@owned String) -> @owned Wrapper<Wrapper<String>>
78+
// WHOLEMOD-LABEL: sil hidden [ossa] @$s17delayed_functions1SV1lSSvpfP : $@convention(thin) (@owned String) -> @owned Wrapper<Wrapper<String>>
79+
80+
// variable initialization expression of S._l
81+
// SINGLE-LABEL: sil hidden [transparent] [ossa] @$s17delayed_functions1SV2_l33_6D0D158845E4BE9792202A0D16664A88LLAA7WrapperVyAGySSGGvpfi : $@convention(thin) () -> @owned String
82+
// WHOLEMOD-LABEL: sil hidden [transparent] [ossa] @$s17delayed_functions1SV2_l33_6D0D158845E4BE9792202A0D16664A88LLAA7WrapperVyAGySSGGvpfi : $@convention(thin) () -> @owned String
83+
@Wrapper
84+
@Wrapper(.random() ? 1 : 2)
85+
var l = ""
86+
}
87+
88+
// The backing init for a projected wrapper can be delayed.
89+
// property wrapper init from projected value of x #1 in takesProjected(_:)
90+
// SINGLE-LABEL: sil hidden [ossa] @$s17delayed_functions14takesProjectedyyAA0D7WrapperVySiGF1xL_SivpfW : $@convention(thin) (Projected<Int>) -> ProjectedWrapper<Int>
91+
// WHOLEMOD-NOT: sil hidden [ossa] @$s17delayed_functions14takesProjectedyyAA0D7WrapperVySiGF1xL_SivpfW : $@convention(thin) (Projected<Int>) -> ProjectedWrapper<Int>
92+
func takesProjected(@ProjectedWrapper _ x: Int) {}
93+
94+
struct HasPrivate {
95+
// These backing initializers can be dropped entirely as they're private and
96+
// unused.
97+
98+
// property wrapper backing initializer of x #1 in HasPrivate.testPrivateWrapper(x:)
99+
// SINGLE-NOT: sil private [ossa] @$s17delayed_functions10HasPrivateV04testD7Wrapper{{.*}}LL1xyAA09ProjectedF0VySiG_tFAFL_SivpfP : $@convention(thin) (Int) -> ProjectedWrapper<Int>
100+
// WHOLEMOD-NOT: sil private [ossa] @$s17delayed_functions10HasPrivateV04testD7Wrapper{{.*}}LL1xyAA09ProjectedF0VySiG_tFAFL_SivpfP : $@convention(thin) (Int) -> ProjectedWrapper<Int>
101+
102+
// property wrapper init from projected value of x #1 in HasPrivate.testPrivateWrapper(x:)
103+
// SINGLE-NOT: sil private [ossa] @$s17delayed_functions10HasPrivateV04testD7Wrapper{{.*}}LL1xyAA09ProjectedF0VySiG_tFAFL_SivpfW : $@convention(thin) (Projected<Int>) -> ProjectedWrapper<Int>
104+
// WHOLEMOD-NOT: sil private [ossa] @$s17delayed_functions10HasPrivateV04testD7Wrapper{{.*}}LL1xyAA09ProjectedF0VySiG_tFAFL_SivpfW : $@convention(thin) (Projected<Int>) -> ProjectedWrapper<Int>
105+
106+
// The function itself needs to be emitted because it can contain user code.
107+
// HasPrivate.testPrivateWrapper(x:)
108+
// SINGLE: sil private [ossa] @$s17delayed_functions10HasPrivateV04testD7Wrapper{{.*}}LL1xyAA09ProjectedF0VySiG_tF : $@convention(method) (ProjectedWrapper<Int>, HasPrivate) -> ()
109+
// WHOLEMOD: sil private [ossa] @$s17delayed_functions10HasPrivateV04testD7Wrapper{{.*}}LL1xyAA09ProjectedF0VySiG_tF : $@convention(method) (ProjectedWrapper<Int>, HasPrivate) -> ()
110+
private func testPrivateWrapper(@ProjectedWrapper x: Int) {}
111+
112+
// property wrapper backing initializer of x #1 in HasPrivate.testFilePrivateWrapper(x:)
113+
// SINGLE-NOT: sil private [ossa] @$s17delayed_functions10HasPrivateV08testFileD7Wrapper{{.*}}LL1xyAA09ProjectedG0VySiG_tFAFL_SivpfP : $@convention(thin) (Int) -> ProjectedWrapper<Int>
114+
// WHOLEMOD-NOT: sil private [ossa] @$s17delayed_functions10HasPrivateV08testFileD7Wrapper{{.*}}LL1xyAA09ProjectedG0VySiG_tFAFL_SivpfP : $@convention(thin) (Int) -> ProjectedWrapper<Int>
115+
116+
// property wrapper init from projected value of x #1 in HasPrivate.testFilePrivateWrapper(x:)
117+
// SINGLE-NOT: sil private [ossa] @$s17delayed_functions10HasPrivateV08testFileD7Wrapper{{.*}}LL1xyAA09ProjectedG0VySiG_tFAFL_SivpfW : $@convention(thin) (Projected<Int>) -> ProjectedWrapper<Int>
118+
// WHOLEMOD-NOT: sil private [ossa] @$s17delayed_functions10HasPrivateV08testFileD7Wrapper{{.*}}LL1xyAA09ProjectedG0VySiG_tFAFL_SivpfW : $@convention(thin) (Projected<Int>) -> ProjectedWrapper<Int>
119+
120+
// The function itself needs to be emitted because it can contain user code.
121+
// HasPrivate.testFilePrivateWrapper(x:)
122+
// SINGLE: sil private [ossa] @$s17delayed_functions10HasPrivateV08testFileD7Wrapper{{.*}}LL1xyAA09ProjectedG0VySiG_tF : $@convention(method) (ProjectedWrapper<Int>, HasPrivate) -> ()
123+
// WHOLEMOD: sil private [ossa] @$s17delayed_functions10HasPrivateV08testFileD7Wrapper{{.*}}LL1xyAA09ProjectedG0VySiG_tF : $@convention(method) (ProjectedWrapper<Int>, HasPrivate) -> ()
124+
fileprivate func testFilePrivateWrapper(@ProjectedWrapper x: Int) {}
125+
}

test/SILGen/property_wrapper_parameter.swift

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
// RUN: %empty-directory(%t)
22
// RUN: %target-swift-frontend -emit-module -o %t -enable-library-evolution %S/Inputs/def_structA.swift
3-
// RUN: %target-swift-emit-silgen %s -I %t | %FileCheck %s
3+
4+
// This uses '-primary-file' to ensure we're conservative with lazy SIL emission.
5+
// RUN: %target-swift-emit-silgen -primary-file %s -I %t | %FileCheck %s
6+
47
import def_structA
58

69
public struct Projection<T> {
@@ -388,6 +391,15 @@ struct HasPrivate {
388391

389392
// CHECK-LABEL: sil private [ossa] @$s26property_wrapper_parameter10HasPrivateV08testFileE7Wrapper{{.*}}LL1xyAA0H0VySiG_tF : $@convention(method) (Wrapper<Int>, HasPrivate) -> ()
390393
fileprivate func testFilePrivateWrapper(@Wrapper x: Int) {}
394+
395+
func usesWrapperFunctions() {
396+
// These are needed to ensure we emit the backing initializers. Otherwise
397+
// lazy SILGen emission is happy to drop them.
398+
testPrivateWrapper(x: 0)
399+
testPrivateWrapper($x: Projection(wrappedValue: 0))
400+
testFilePrivateWrapper(x: 0)
401+
testFilePrivateWrapper($x: Projection(wrappedValue: 0))
402+
}
391403
}
392404

393405
@propertyWrapper

0 commit comments

Comments
 (0)