Skip to content

Commit 1b086e1

Browse files
committed
Sema: Embedded functions are fragile unless marked otherwise
Consider functions in embedded mode to be fragile unless marked with `@_neverEmitIntoClient`. Basically treating them as if they were `@_alwaysEmitIntoClient` by default. A fragile function cannot reference restricted imports such as `@_implementationOnly`, `internal import`, etc. We consider them as fragile only for type-checking, at the SIL level they remain treated as normal functions like before. This allows the later optimization to work as expected. We may want to likely align both ends of the compiler once this is properly supported in sema. This is enabled only in embedded. Fragile functions in library-evolution are marked with attributes and are already checked. We do not need this is non-embedded non-library-evolution as CMO handles inlining and already takes into consideration `@_implementationOnly` imports. Note: We'll need a similar check for memory layouts exposed to clients. We can still see compiler crashes and miscompiles in this scenario. This will apply to both embedded and non-library-evolution modes. That will likely need to extend the `@_neverEmitIntoClient` attribute and may require a staged deployment. rdar://161365361
1 parent a956308 commit 1b086e1

File tree

9 files changed

+466
-24
lines changed

9 files changed

+466
-24
lines changed

include/swift/AST/DeclContext.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ struct ConformanceDiagnostic {
202202
ProtocolDecl *ExistingExplicitProtocol;
203203
};
204204

205-
/// Used in diagnostic %selects.
205+
/// Used in diagnostic %selects via FRAGILE_FUNC_KIND.
206206
struct FragileFunctionKind {
207207
enum Kind : unsigned {
208208
Transparent,
@@ -211,6 +211,7 @@ struct FragileFunctionKind {
211211
DefaultArgument,
212212
PropertyInitializer,
213213
BackDeploy,
214+
EmbeddedAlwaysEmitIntoClient,
214215
None
215216
};
216217

include/swift/AST/DiagnosticsSema.def

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7312,7 +7312,8 @@ ERROR(usable_from_inline_attr_in_protocol,none,
73127312
"an '@_alwaysEmitIntoClient' function|" \
73137313
"a default argument value|" \
73147314
"a property initializer in a '@frozen' type|" \
7315-
"a '@backDeployed' function'}"
7315+
"a '@backDeployed' function|" \
7316+
"an embedded function not marked '@_neverEmitIntoClient'}"
73167317

73177318
ERROR(local_type_in_inlinable_function,
73187319
none, "type %0 cannot be nested inside " FRAGILE_FUNC_KIND "1",

lib/AST/DeclContext.cpp

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -488,6 +488,11 @@ ResilienceExpansion DeclContext::getResilienceExpansion() const {
488488
case FragileFunctionKind::PropertyInitializer:
489489
case FragileFunctionKind::BackDeploy:
490490
return ResilienceExpansion::Minimal;
491+
492+
/// Embedded functions are treated as fragile for diagnostics only.
493+
/// For code gen they are treated as normal and optimized later.
494+
case FragileFunctionKind::EmbeddedAlwaysEmitIntoClient:
495+
491496
case FragileFunctionKind::None:
492497
return ResilienceExpansion::Maximal;
493498
}
@@ -551,14 +556,32 @@ swift::FragileFunctionKindRequest::evaluate(Evaluator &evaluator,
551556
if (AFD->getDeclContext()->isLocalContext())
552557
continue;
553558

559+
// Delay checking the implicit conditions after explicit declarations.
560+
auto checkEmbeddedAlwaysEmitIntoClient = [&](const ValueDecl *VD) {
561+
if (!VD->getASTContext().LangOpts.hasFeature(Feature::Embedded))
562+
return FragileFunctionKind::None;
563+
564+
bool funcIsNEIC = VD->isNeverEmittedIntoClient();
565+
bool storageIsNEIC = false;
566+
if (auto accessor = dyn_cast<AccessorDecl>(VD))
567+
storageIsNEIC = accessor->getStorage()->isNeverEmittedIntoClient();
568+
569+
// Accessors are implicitly EmbeddedAlwaysEmitIntoClient if neither the
570+
// accessor or starage is marked @_neverEmitIntoClient.
571+
if (!funcIsNEIC && !storageIsNEIC)
572+
return FragileFunctionKind::EmbeddedAlwaysEmitIntoClient;
573+
return FragileFunctionKind::None;
574+
};
575+
554576
auto funcAccess =
555577
AFD->getFormalAccessScope(/*useDC=*/nullptr,
556578
/*treatUsableFromInlineAsPublic=*/true);
557579

558580
// If the function is not externally visible, we will not be serializing
559581
// its body.
560582
if (!funcAccess.isPublic()) {
561-
return {FragileFunctionKind::None};
583+
// For non-public decls, only check embedded mode correctness.
584+
return {checkEmbeddedAlwaysEmitIntoClient(AFD)};
562585
}
563586

564587
// If the function is public, @_transparent implies @inlinable.
@@ -592,6 +615,10 @@ swift::FragileFunctionKindRequest::evaluate(Evaluator &evaluator,
592615
return {FragileFunctionKind::BackDeploy};
593616
}
594617
}
618+
619+
auto implicitKind = checkEmbeddedAlwaysEmitIntoClient(AFD);
620+
if (implicitKind != FragileFunctionKind::None)
621+
return {implicitKind};
595622
}
596623
}
597624

lib/AST/TypeCheckRequests.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -687,6 +687,9 @@ void swift::simple_display(llvm::raw_ostream &out,
687687
case FragileFunctionKind::BackDeploy:
688688
out << "backDeploy";
689689
return;
690+
case FragileFunctionKind::EmbeddedAlwaysEmitIntoClient:
691+
out << "embeddedAlwaysEmitIntoClient";
692+
return;
690693
case FragileFunctionKind::None:
691694
out << "none";
692695
return;

lib/Sema/ResilienceDiagnostics.cpp

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,14 @@ bool TypeChecker::diagnoseInlinableDeclRefAccess(SourceLoc loc,
9797
return false;
9898
}
9999

100+
// Embedded functions can reference non-public decls as they are visible
101+
// to clients. Still report references to decls imported non-publicly
102+
// to enforce access-level on imports.
103+
ImportAccessLevel problematicImport = D->getImportAccessFrom(DC);
104+
if (fragileKind.kind == FragileFunctionKind::EmbeddedAlwaysEmitIntoClient &&
105+
!problematicImport)
106+
return false;
107+
100108
DowngradeToWarning downgradeToWarning = DowngradeToWarning::No;
101109

102110
// Swift 4.2 did not perform any checks for type aliases.
@@ -127,7 +135,6 @@ bool TypeChecker::diagnoseInlinableDeclRefAccess(SourceLoc loc,
127135

128136
Context.Diags.diagnose(D, diag::resilience_decl_declared_here, D);
129137

130-
ImportAccessLevel problematicImport = D->getImportAccessFrom(DC);
131138
if (problematicImport.has_value() &&
132139
problematicImport->accessLevel < D->getFormalAccess()) {
133140
Context.Diags.diagnose(problematicImport->importLoc,
Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
/// Test @_implementationOnly internal import exportability diagnostics in embedded mode.
2+
3+
// RUN: %empty-directory(%t)
4+
// RUN: %target-swift-frontend -emit-module -o %t/indirects.swiftmodule \
5+
// RUN: %S/Inputs/implementation-only-imports/indirects.swift \
6+
// RUN: -swift-version 5 -target arm64-apple-macosx15.0 \
7+
// RUN: -enable-experimental-feature Embedded
8+
// RUN: %target-swift-frontend -emit-module -o %t/directs.swiftmodule -I %t \
9+
// RUN: %S/Inputs/implementation-only-imports/directs.swift \
10+
// RUN: -swift-version 5 -target arm64-apple-macosx15.0 \
11+
// RUN: -enable-experimental-feature Embedded
12+
13+
// RUN: %target-swift-frontend -typecheck -verify %s -I %t \
14+
// RUN: -swift-version 5 -target arm64-apple-macosx15.0 \
15+
// RUN: -enable-experimental-feature Embedded
16+
17+
// REQUIRES: swift_feature_Embedded
18+
19+
@_implementationOnly internal import directs
20+
// expected-warning @-1 {{using '@_implementationOnly' without enabling library evolution for 'main' may lead to instability during execution}}
21+
// expected-note @-2 19 {{struct 'StructFromDirect' imported as 'internal' from 'directs' here}}
22+
// expected-note @-3 12 {{initializer 'init()' imported as 'internal' from 'directs' here}}
23+
import indirects
24+
25+
internal func localInternalFunc() {} // expected-note {{global function 'localInternalFunc()' is not '@usableFromInline' or public}}
26+
27+
@inlinable
28+
public func explicitlyInlinable(arg: StructFromDirect = StructFromDirect()) {
29+
// expected-error @-1 {{initializer 'init()' is internal and cannot be referenced from a default argument value}}
30+
// expected-error @-2 {{struct 'StructFromDirect' is internal and cannot be referenced from a default argument value}}
31+
// expected-error @-3 {{struct 'StructFromDirect' is internal and cannot be referenced from an '@inlinable' function}}
32+
// expected-error @-4 {{function cannot be declared public because its parameter uses an internal type}}
33+
// expected-note @-5 {{struct 'StructFromDirect' is imported by this file as 'internal' from 'directs'}}
34+
_ = StructFromDirect() // expected-error {{initializer 'init()' is internal and cannot be referenced from an '@inlinable' function}}
35+
// expected-error@-1 {{struct 'StructFromDirect' is internal and cannot be referenced from an '@inlinable' function}}
36+
37+
if (true) {
38+
_ = StructFromDirect() // expected-error {{initializer 'init()' is internal and cannot be referenced from an '@inlinable' function}}
39+
// expected-error@-1 {{struct 'StructFromDirect' is internal and cannot be referenced from an '@inlinable' function}}
40+
}
41+
42+
func nested() {
43+
_ = StructFromDirect() // expected-error {{initializer 'init()' is internal and cannot be referenced from an '@inlinable' function}}
44+
// expected-error@-1 {{struct 'StructFromDirect' is internal and cannot be referenced from an '@inlinable' function}}
45+
}
46+
nested()
47+
48+
localInternalFunc() // expected-error {{global function 'localInternalFunc()' is internal and cannot be referenced from an '@inlinable' function}}
49+
50+
explicitlyInlinable()
51+
implicitlyInlinablePublic()
52+
implicitlyInlinablePrivate() // expected-error {{global function 'implicitlyInlinablePrivate(arg:)' is private and cannot be referenced from an '@inlinable' function}}
53+
explicitNonInliable()
54+
}
55+
56+
public func implicitlyInlinablePublic(arg: StructFromDirect = StructFromDirect()) {
57+
// expected-error @-1 {{initializer 'init()' is internal and cannot be referenced from a default argument value}}
58+
// expected-error @-2 {{struct 'StructFromDirect' is internal and cannot be referenced from a default argument value}}
59+
// expected-error @-3 {{struct 'StructFromDirect' is internal and cannot be referenced from an embedded function not marked '@_neverEmitIntoClient'}}
60+
// expected-error @-4 {{function cannot be declared public because its parameter uses an internal type}}
61+
// expected-note @-5 {{struct 'StructFromDirect' is imported by this file as 'internal' from 'directs'}}
62+
_ = StructFromDirect() // expected-error {{initializer 'init()' is internal and cannot be referenced from an embedded function not marked '@_neverEmitIntoClient'}}
63+
// expected-error@-1 {{struct 'StructFromDirect' is internal and cannot be referenced from an embedded function not marked '@_neverEmitIntoClient'}}
64+
65+
if (true) {
66+
_ = StructFromDirect() // expected-error {{initializer 'init()' is internal and cannot be referenced from an embedded function not marked '@_neverEmitIntoClient'}}
67+
// expected-error@-1 {{struct 'StructFromDirect' is internal and cannot be referenced from an embedded function not marked '@_neverEmitIntoClient'}}
68+
}
69+
70+
func nested() {
71+
_ = StructFromDirect() // expected-error {{initializer 'init()' is internal and cannot be referenced from an embedded function not marked '@_neverEmitIntoClient'}}
72+
// expected-error@-1 {{struct 'StructFromDirect' is internal and cannot be referenced from an embedded function not marked '@_neverEmitIntoClient'}}
73+
}
74+
nested()
75+
76+
localInternalFunc()
77+
78+
explicitlyInlinable()
79+
implicitlyInlinablePublic()
80+
implicitlyInlinablePrivate()
81+
explicitNonInliable()
82+
}
83+
84+
private func implicitlyInlinablePrivate(arg: StructFromDirect = StructFromDirect()) {
85+
// expected-error @-1 {{struct 'StructFromDirect' is internal and cannot be referenced from an embedded function not marked '@_neverEmitIntoClient'}}
86+
// expected-note @-2 {{global function 'implicitlyInlinablePrivate(arg:)' is not '@usableFromInline' or public}}
87+
_ = StructFromDirect() // expected-error {{initializer 'init()' is internal and cannot be referenced from an embedded function not marked '@_neverEmitIntoClient'}}
88+
// expected-error@-1 {{struct 'StructFromDirect' is internal and cannot be referenced from an embedded function not marked '@_neverEmitIntoClient'}}
89+
90+
if (true) {
91+
_ = StructFromDirect() // expected-error {{initializer 'init()' is internal and cannot be referenced from an embedded function not marked '@_neverEmitIntoClient'}}
92+
// expected-error@-1 {{struct 'StructFromDirect' is internal and cannot be referenced from an embedded function not marked '@_neverEmitIntoClient'}}
93+
}
94+
95+
func nested() {
96+
_ = StructFromDirect() // expected-error {{initializer 'init()' is internal and cannot be referenced from an embedded function not marked '@_neverEmitIntoClient'}}
97+
// expected-error@-1 {{struct 'StructFromDirect' is internal and cannot be referenced from an embedded function not marked '@_neverEmitIntoClient'}}
98+
}
99+
nested()
100+
101+
localInternalFunc()
102+
103+
explicitlyInlinable()
104+
implicitlyInlinablePublic()
105+
implicitlyInlinablePrivate()
106+
explicitNonInliable()
107+
}
108+
109+
@_neverEmitIntoClient
110+
public func explicitNonInliable(arg: StructFromDirect = StructFromDirect()) {
111+
// expected-error @-1 {{initializer 'init()' is internal and cannot be referenced from a default argument value}}
112+
// expected-error @-2 {{struct 'StructFromDirect' is internal and cannot be referenced from a default argument value}}
113+
// expected-error @-3 {{cannot use struct 'StructFromDirect' here; 'directs' has been imported as implementation-only}}
114+
// expected-error @-4 {{function cannot be declared public because its parameter uses an internal type}}
115+
// expected-note @-5 {{struct 'StructFromDirect' is imported by this file as 'internal' from 'directs'}}
116+
_ = StructFromDirect()
117+
118+
if (true) {
119+
_ = StructFromDirect()
120+
}
121+
122+
@_neverEmitIntoClient
123+
func nested() {
124+
_ = StructFromDirect()
125+
}
126+
nested()
127+
128+
localInternalFunc()
129+
130+
explicitlyInlinable()
131+
implicitlyInlinablePublic()
132+
implicitlyInlinablePrivate()
133+
explicitNonInliable()
134+
}
135+
136+
@_neverEmitIntoClient
137+
internal func explicitNonInliableInternal(arg: StructFromDirect = StructFromDirect()) {
138+
_ = StructFromDirect()
139+
140+
if (true) {
141+
_ = StructFromDirect()
142+
}
143+
144+
@_neverEmitIntoClient
145+
func nested() {
146+
_ = StructFromDirect()
147+
}
148+
nested()
149+
150+
localInternalFunc()
151+
152+
explicitlyInlinable()
153+
implicitlyInlinablePublic()
154+
implicitlyInlinablePrivate()
155+
explicitNonInliable()
156+
}
157+
158+
public func legalAccessToIndirect(arg: StructFromIndirect = StructFromIndirect()) {
159+
_ = StructFromIndirect()
160+
161+
if (true) {
162+
_ = StructFromIndirect()
163+
}
164+
165+
func nested() {
166+
_ = StructFromIndirect()
167+
}
168+
nested()
169+
}
170+
171+
public struct ExposedLayoutPublic {
172+
public var publicField: StructFromDirect // expected-error {{property cannot be declared public because its type uses an internal type}}
173+
// expected-error @-1 {{cannot use struct 'StructFromDirect' here; 'directs' has been imported as implementation-only}}
174+
// expected-note @-2 {{struct 'StructFromDirect' is imported by this file as 'internal' from 'directs'}}
175+
176+
private var privateField: StructFromDirect
177+
}
178+
179+
private struct ExposedLayoutPrivate {
180+
private var privateField: StructFromDirect
181+
}

0 commit comments

Comments
 (0)