Skip to content

Commit 3e2d7d4

Browse files
committed
SIL: Serialize bodies of local functions inside @_transparent functions
A transparent function might be deserialized and inlined into a function in another module, which would cause problems if the function referenced local functions. Previously we would force local functions to have public linkage instead, which worked, but was not resilient if the body of the transparent function changed in the module that contained it. Add a library evolution test ensuring that such a change is resilient now. Part of https://bugs.swift.org/browse/SR-267.
1 parent 187faca commit 3e2d7d4

10 files changed

+92
-63
lines changed

include/swift/SIL/SILDeclRef.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,8 @@ struct SILDeclRef {
264264

265265
/// \brief True if the function should be treated as transparent.
266266
bool isTransparent() const;
267+
/// \brief True if the function should have its body serialized.
268+
bool isFragile() const;
267269
/// \brief True if the function has noinline attribute.
268270
bool isNoinline() const;
269271
/// \brief True if the function has __always inline attribute.

lib/SIL/SILDeclRef.cpp

Lines changed: 40 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -240,34 +240,6 @@ Optional<AnyFunctionRef> SILDeclRef::getAnyFunctionRef() const {
240240
return AnyFunctionRef(loc.get<AbstractClosureExpr*>());
241241
}
242242

243-
static SILLinkage getLinkageForLocalContext(DeclContext *dc) {
244-
auto isClangImported = [](AbstractFunctionDecl *fn) -> bool {
245-
if (fn->hasClangNode())
246-
return true;
247-
if (auto func = dyn_cast<FuncDecl>(fn))
248-
if (auto storage = func->getAccessorStorageDecl())
249-
return storage->hasClangNode();
250-
return false;
251-
};
252-
253-
while (!dc->isModuleScopeContext()) {
254-
// Local definitions in transparent contexts are forced public because
255-
// external references to them can be exposed by mandatory inlining.
256-
// For Clang-imported decls, though, the closure should get re-synthesized
257-
// on use.
258-
if (auto fn = dyn_cast<AbstractFunctionDecl>(dc))
259-
if (fn->isTransparent() && !isClangImported(fn))
260-
return SILLinkage::Public;
261-
// Check that this local context is not itself in a local transparent
262-
// context.
263-
dc = dc->getParent();
264-
}
265-
266-
// FIXME: Once we have access control at the AST level, we should not assume
267-
// shared always, but rather base it off of the local decl context.
268-
return SILLinkage::Shared;
269-
}
270-
271243
bool SILDeclRef::isThunk() const {
272244
return isCurried || isForeignToNativeThunk() || isNativeToForeignThunk();
273245
}
@@ -300,6 +272,8 @@ bool SILDeclRef::isClangGenerated() const {
300272

301273
auto clangNode = getDecl()->getClangNode().getAsDecl();
302274
if (auto nd = dyn_cast_or_null<clang::NamedDecl>(clangNode)) {
275+
// ie, 'static inline' functions for which we must ask Clang to emit a body
276+
// for explicitly
303277
if (!nd->isExternallyVisible())
304278
return true;
305279
}
@@ -308,25 +282,26 @@ bool SILDeclRef::isClangGenerated() const {
308282
}
309283

310284
SILLinkage SILDeclRef::getLinkage(ForDefinition_t forDefinition) const {
311-
// Anonymous functions have local linkage.
312-
if (auto closure = getAbstractClosureExpr())
313-
return getLinkageForLocalContext(closure->getParent());
285+
// Anonymous functions have shared linkage.
286+
// FIXME: This should really be the linkage of the parent function.
287+
if (getAbstractClosureExpr())
288+
return SILLinkage::Shared;
314289

315-
// Native function-local declarations have local linkage.
290+
// Native function-local declarations have shared linkage.
316291
// FIXME: @objc declarations should be too, but we currently have no way
317292
// of marking them "used" other than making them external.
318293
ValueDecl *d = getDecl();
319294
DeclContext *moduleContext = d->getDeclContext();
320295
while (!moduleContext->isModuleScopeContext()) {
321296
if (!isForeign && moduleContext->isLocalContext())
322-
return getLinkageForLocalContext(moduleContext);
297+
return SILLinkage::Shared;
323298
moduleContext = moduleContext->getParent();
324299
}
325300

326301
// Currying and calling convention thunks have shared linkage.
327302
if (isThunk())
328-
// If a function declares a @_cdecl name, its native-to-foreign thunk is
329-
// exported with the visibility of the function.
303+
// If a function declares a @_cdecl name, its native-to-foreign thunk
304+
// is exported with the visibility of the function.
330305
if (!isNativeToForeignThunk() || !d->getAttrs().hasAttribute<CDeclAttr>())
331306
return SILLinkage::Shared;
332307

@@ -383,6 +358,36 @@ bool SILDeclRef::isTransparent() const {
383358
return hasDecl() ? getDecl()->isTransparent() : false;
384359
}
385360

361+
/// \brief True if the function should have its body serialized.
362+
bool SILDeclRef::isFragile() const {
363+
DeclContext *dc;
364+
if (auto closure = getAbstractClosureExpr())
365+
dc = closure->getLocalContext();
366+
else
367+
dc = getDecl()->getDeclContext();
368+
369+
while (!dc->isModuleScopeContext()) {
370+
// Local definitions in transparent contexts are fragile because
371+
// external references to them can be exposed by mandatory inlining.
372+
if (auto fn = dyn_cast<AbstractFunctionDecl>(dc))
373+
if (fn->isTransparent() &&
374+
fn->getEffectiveAccess() == Accessibility::Public)
375+
return true;
376+
// Check that this local context is not itself in a local transparent
377+
// context.
378+
dc = dc->getParent();
379+
}
380+
381+
// Externally-visible transparent functions are fragile.
382+
if (hasDecl())
383+
if (auto fn = dyn_cast<AbstractFunctionDecl>(getDecl()))
384+
if (fn->isTransparent() &&
385+
fn->getEffectiveAccess() == Accessibility::Public)
386+
return true;
387+
388+
return false;
389+
}
390+
386391
/// \brief True if the function has noinline attribute.
387392
bool SILDeclRef::isNoinline() const {
388393
if (!hasDecl())

lib/SIL/SILModule.cpp

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -328,16 +328,16 @@ SILFunction *SILModule::getOrCreateFunction(SILLocation loc,
328328
return fn;
329329
}
330330

331-
IsTransparent_t IsTrans = constant.isTransparent()?
332-
IsTransparent : IsNotTransparent;
333-
IsFragile_t IsFrag = IsNotFragile;
334-
if (IsTrans == IsTransparent && (linkage == SILLinkage::Public
335-
|| linkage == SILLinkage::PublicExternal)) {
336-
IsFrag = IsFragile;
337-
}
338-
339-
EffectsKind EK = constant.hasEffectsAttribute() ?
340-
constant.getEffectsAttribute() : EffectsKind::Unspecified;
331+
IsTransparent_t IsTrans = constant.isTransparent()
332+
? IsTransparent
333+
: IsNotTransparent;
334+
IsFragile_t IsFrag = constant.isFragile()
335+
? IsFragile
336+
: IsNotFragile;
337+
338+
EffectsKind EK = constant.hasEffectsAttribute()
339+
? constant.getEffectsAttribute()
340+
: EffectsKind::Unspecified;
341341

342342
Inline_t inlineStrategy = InlineDefault;
343343
if (constant.isNoinline())

test/SILGen/c_materializeForSet_linkage.swift

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,11 @@ extension NSReferencePoint: Pointable {}
1616
// Make sure synthesized materializeForSet and its callbacks have shared linkage
1717
// for properties imported from Clang
1818

19-
// CHECK-LABEL: sil shared [transparent] @_TFVSC7NSPointm1xSf
20-
// CHECK-LABEL: sil shared [transparent] @_TFVSC7NSPointm1ySf
19+
// CHECK-LABEL: sil shared [transparent] [fragile] @_TFVSC7NSPointm1xSf
20+
// CHECK-LABEL: sil shared [transparent] [fragile] @_TFVSC7NSPointm1ySf
2121

22-
// CHECK-LABEL: sil shared [transparent] @_TFCSo16NSReferencePointm1xSf
23-
// CHECK-LABEL: sil shared [transparent] @_TFCSo16NSReferencePointm1ySf
22+
// CHECK-LABEL: sil shared [transparent] [fragile] @_TFCSo16NSReferencePointm1xSf
23+
// CHECK-LABEL: sil shared [transparent] [fragile] @_TFCSo16NSReferencePointm1ySf
2424

25-
// CHECK-LABEL: sil shared [transparent] @_TFFCSo16NSReferencePointm1xSfU_T_
26-
// CHECK-LABEL: sil shared [transparent] @_TFFCSo16NSReferencePointm1ySfU_T_
25+
// CHECK-LABEL: sil shared [transparent] [fragile] @_TFFCSo16NSReferencePointm1xSfU_T_
26+
// CHECK-LABEL: sil shared [transparent] [fragile] @_TFFCSo16NSReferencePointm1ySfU_T_

test/SILGen/cf.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,9 +59,9 @@ protocol Impedance {
5959
extension CCImpedance: Impedance {}
6060

6161
// CHECK-LABEL: sil hidden [transparent] [thunk] @_TTWVSC11CCImpedance2cf9ImpedanceS0_FS1_g4realwx9Component
62-
// CHECK-LABEL: sil shared [transparent] @_TFVSC11CCImpedanceg4realSd
62+
// CHECK-LABEL: sil shared [transparent] [fragile] @_TFVSC11CCImpedanceg4realSd
6363
// CHECK-LABEL: sil hidden [transparent] [thunk] @_TTWVSC11CCImpedance2cf9ImpedanceS0_FS1_g4imagwx9Component
64-
// CHECK-LABEL: sil shared [transparent] @_TFVSC11CCImpedanceg4imagSd
64+
// CHECK-LABEL: sil shared [transparent] [fragile] @_TFVSC11CCImpedanceg4imagSd
6565

6666
class MyMagnetism : CCMagnetismModel {
6767
// CHECK-LABEL: sil hidden [thunk] @_TToFC2cf11MyMagnetism15getRefrigerator{{.*}} : $@convention(objc_method) (MyMagnetism) -> @autoreleased CCRefrigerator
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// RUN: %target-swift-frontend -emit-silgen -import-objc-header %S/Inputs/array_typedef.h %s | FileCheck %s
22

3-
// CHECK-LABEL: sil shared [transparent] @_TFVSC4NameC{{.*}} : $@convention(thin) (UInt8, UInt8, UInt8, UInt8, @thin Name.Type) -> Name
3+
// CHECK-LABEL: sil shared [transparent] [fragile] @_TFVSC4NameC{{.*}} : $@convention(thin) (UInt8, UInt8, UInt8, UInt8, @thin Name.Type) -> Name
44
func useImportedArrayTypedefInit() -> Name {
55
return Name(name: (0, 0, 0, 0))
66
}

test/SILGen/nsmanaged-witness.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,5 +61,5 @@ extension Foo: NativeIntProperty {}
6161
// TODO: We can't emit a vtable entry for materializeForSet for ObjC types.
6262
// CHECK-NOT: class_method {{.*}}Foo{{.*}}intProperty{{.*}}materializeForSet
6363

64-
// CHECK-LABEL: sil shared [transparent] @_TFCSo3Foom11intPropertyVs5Int32
64+
// CHECK-LABEL: sil shared [transparent] [fragile] @_TFCSo3Foom11intPropertyVs5Int32
6565

test/SILGen/transparent_attribute.swift

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -181,19 +181,19 @@ struct testVarDeclShortenedSyntax {
181181
}
182182
// CHECK: sil hidden [transparent] @_TF21transparent_attributeg22transparentOnGlobalVarSi
183183

184-
// Local functions in transparent context have public linkage.
185-
@_transparent func foo() {
186-
// CHECK-LABEL: sil @_TFF21transparent_attribute3fooFT_T_L_3barFT_T_ : $@convention(thin) () -> ()
184+
// Local functions in transparent context are fragile.
185+
@_transparent public func foo() {
186+
// CHECK-LABEL: sil shared [fragile] @_TFF21transparent_attribute3fooFT_T_L_3barFT_T_ : $@convention(thin) () -> ()
187187
func bar() {}
188188
bar()
189189

190-
// CHECK-LABEL: sil @_TFF21transparent_attribute3fooFT_T_U_FT_T_ : $@convention(thin) () -> () {
190+
// CHECK-LABEL: sil shared [fragile] @_TFF21transparent_attribute3fooFT_T_U_FT_T_ : $@convention(thin) () -> () {
191191
let f: () -> () = {}
192192
f()
193193

194-
// CHECK-LABEL: sil @_TFF21transparent_attribute3fooFT_T_L_3zimFT_T_ : $@convention(thin) () -> () {
194+
// CHECK-LABEL: sil shared [fragile] @_TFF21transparent_attribute3fooFT_T_L_3zimFT_T_ : $@convention(thin) () -> () {
195195
func zim() {
196-
// CHECK-LABEL: sil @_TFFF21transparent_attribute3fooFT_T_L_3zimFT_T_L_4zangFT_T_ : $@convention(thin) () -> () {
196+
// CHECK-LABEL: sil shared [fragile] @_TFFF21transparent_attribute3fooFT_T_L_3zimFT_T_L_4zangFT_T_ : $@convention(thin) () -> () {
197197
func zang() {
198198
}
199199
zang()

validation-test/Evolution/Inputs/function_change_transparent_body.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,15 @@
77
#endif
88
}
99

10+
@_transparent public func getFunction(x: Int) -> Int -> Int {
11+
// The mangled name and calling convention of the local function
12+
// will change -- so we must serialize it and inline it into
13+
// the calling module
14+
15+
#if BEFORE
16+
func id(y: Int) -> Int { return x * y }
17+
#else
18+
func id(y: Int) -> Int { return y }
19+
#endif
20+
return id
21+
}

validation-test/Evolution/test_function_change_transparent_body.swift

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,19 @@ var ChangeTransparentBodyTest = TestSuite("ChangeTransparentBody")
2121
ChangeTransparentBodyTest.test("ChangeTransparentBody") {
2222

2323
#if BEFORE
24-
expectEqual(getBuildVersion(), 0)
24+
expectEqual(0, getBuildVersion())
2525
#else
26-
expectEqual(getBuildVersion(), 1)
26+
expectEqual(1, getBuildVersion())
27+
#endif
28+
29+
}
30+
31+
ChangeTransparentBodyTest.test("ChangeTransparentClosure") {
32+
33+
#if BEFORE
34+
expectEqual(202, getFunction(2)(101))
35+
#else
36+
expectEqual(101, getFunction(2)(101))
2737
#endif
2838

2939
}

0 commit comments

Comments
 (0)