Skip to content

Commit 5186971

Browse files
committed
Frontend: Introduce -experimental-serialize-external-decls-only option.
This option is designed to be used in conjunction with `-experimental-lazy-typecheck` and `-experimental-skip-all-function-bodies` when emitting a resilient module. The emitted binary module should contain only the decls needed by clients and should contain roughly the same contents as it would if the corresponding swiftinterface were emitted instead and then built. This functionality is a work in progress. Some parts of the AST may still get typechecked unnecessarily. Additionally, serialization does not trigger the appropriate typechecking requests for some ASTs and then fails due to missing types. Resolves rdar://114230586
1 parent 3a02629 commit 5186971

File tree

10 files changed

+144
-7
lines changed

10 files changed

+144
-7
lines changed

include/swift/Frontend/FrontendOptions.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,6 @@ class FrontendOptions {
267267
/// \see ModuleDecl::arePrivateImportsEnabled
268268
bool EnablePrivateImports = false;
269269

270-
271270
/// Indicates whether we add implicit dynamic.
272271
///
273272
/// \see ModuleDecl::isImplicitDynamicEnabled
@@ -322,6 +321,10 @@ class FrontendOptions {
322321
/// times) when compiling a module interface?
323322
bool SerializeModuleInterfaceDependencyHashes = false;
324323

324+
/// Should we only serialize decls that may be referenced externally in the
325+
/// binary module?
326+
bool SerializeExternalDeclsOnly = false;
327+
325328
/// Should we warn if an imported module needed to be rebuilt from a
326329
/// module interface file?
327330
bool RemarkOnRebuildFromModuleInterface = false;

include/swift/Option/FrontendOptions.td

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,10 @@ def serialize_debugging_options : Flag<["-"], "serialize-debugging-options">,
166166
def serialized_path_obfuscate : Separate<["-"], "serialized-path-obfuscate">,
167167
HelpText<"Remap source paths in debug info">, MetaVarName<"<prefix=replacement>">;
168168

169+
def experimental_serialize_external_decls_only
170+
: Flag<["-"], "experimental-serialize-external-decls-only">,
171+
HelpText<"Only serialize decls that should be exposed to clients">;
172+
169173
def empty_abi_descriptor : Flag<["-"], "empty-abi-descriptor">,
170174
HelpText<"Avoid printing actual module content into ABI descriptor file">;
171175

include/swift/Serialization/SerializationOptions.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,7 @@ namespace swift {
156156
bool StaticLibrary = false;
157157
bool HermeticSealAtLink = false;
158158
bool IsOSSA = false;
159+
bool SerializeExternalDeclsOnly = false;
159160
};
160161

161162
} // end namespace swift

lib/Frontend/ArgsToFrontendOptionsConverter.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,8 @@ bool ArgsToFrontendOptionsConverter::convert(
318318
A->getOption().matches(OPT_serialize_debugging_options);
319319
}
320320

321+
Opts.SerializeExternalDeclsOnly |=
322+
Args.hasArg(OPT_experimental_serialize_external_decls_only);
321323
Opts.DebugPrefixSerializedDebuggingOptions |=
322324
Args.hasArg(OPT_prefix_serialized_debugging_options);
323325
Opts.EnableSourceImport |= Args.hasArg(OPT_enable_source_import);

lib/Frontend/Frontend.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,9 @@ SerializationOptions CompilerInvocation::computeSerializationOptions(
225225

226226
serializationOpts.IsOSSA = getSILOptions().EnableOSSAModules;
227227

228+
serializationOpts.SerializeExternalDeclsOnly =
229+
opts.SerializeExternalDeclsOnly;
230+
228231
return serializationOpts;
229232
}
230233

lib/Serialization/Serialization.cpp

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3258,6 +3258,7 @@ class Serializer::DeclSerializer : public DeclVisitor<DeclSerializer> {
32583258
}
32593259
}
32603260

3261+
public:
32613262
/// Determine if \p decl is safe to deserialize when it's public
32623263
/// or otherwise needed by the client in normal builds, this should usually
32633264
/// correspond to logic in type-checking ensuring these safe decls don't
@@ -3336,6 +3337,7 @@ class Serializer::DeclSerializer : public DeclVisitor<DeclSerializer> {
33363337
return false;
33373338
}
33383339

3340+
private:
33393341
/// Write a \c DeserializationSafetyLayout record only when \p decl is unsafe
33403342
/// to deserialize.
33413343
///
@@ -3505,6 +3507,9 @@ class Serializer::DeclSerializer : public DeclVisitor<DeclSerializer> {
35053507

35063508
SmallVector<DeclID, 16> memberIDs;
35073509
for (auto member : members) {
3510+
if (S.shouldSkipDecl(member))
3511+
continue;
3512+
35083513
if (!shouldSerializeMember(member))
35093514
continue;
35103515

@@ -4888,6 +4893,14 @@ static bool canSkipWhenInvalid(const Decl *D) {
48884893
return false;
48894894
}
48904895

4896+
bool Serializer::shouldSkipDecl(const Decl *D) const {
4897+
if (Options.SerializeExternalDeclsOnly &&
4898+
!DeclSerializer::isDeserializationSafe(D))
4899+
return true;
4900+
4901+
return false;
4902+
}
4903+
48914904
void Serializer::writeASTBlockEntity(const Decl *D) {
48924905
using namespace decls_block;
48934906

@@ -6358,6 +6371,9 @@ void Serializer::writeAST(ModuleOrSourceFile DC) {
63586371
continue;
63596372
}
63606373

6374+
if (shouldSkipDecl(D))
6375+
continue;
6376+
63616377
if (auto VD = dyn_cast<ValueDecl>(D)) {
63626378
if (!VD->hasName())
63636379
continue;
@@ -6406,6 +6422,8 @@ void Serializer::writeAST(ModuleOrSourceFile DC) {
64066422
nextFile->getOpaqueReturnTypeDecls(opaqueReturnTypeDecls);
64076423

64086424
for (auto TD : localTypeDecls) {
6425+
if (shouldSkipDecl(TD))
6426+
continue;
64096427

64106428
// FIXME: We should delay parsing function bodies so these type decls
64116429
// don't even get added to the file.
@@ -6436,6 +6454,9 @@ void Serializer::writeAST(ModuleOrSourceFile DC) {
64366454
}
64376455

64386456
for (auto OTD : opaqueReturnTypeDecls) {
6457+
if (shouldSkipDecl(OTD))
6458+
continue;
6459+
64396460
// FIXME: We should delay parsing function bodies so these type decls
64406461
// don't even get added to the file.
64416462
if (OTD->getDeclContext()->getInnermostSkippedFunctionContext())

lib/Serialization/Serialization.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,9 @@ class Serializer : public SerializerBase {
332332
/// Check if a decl is cross-referenced.
333333
bool isDeclXRef(const Decl *D) const;
334334

335+
/// Check if a decl should be skipped during serialization.
336+
bool shouldSkipDecl(const Decl *D) const;
337+
335338
/// Writes a reference to a decl in another module.
336339
void writeCrossReference(const DeclContext *DC, uint32_t pathLen = 1);
337340

test/Driver/emit-interface.swift

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,3 @@
2626
// CHECK-FILELIST: swift{{(-frontend|c)?(\.exe)?"?}} -frontend
2727
// CHECK-FILELIST-SAME: -supplementary-output-file-map
2828
// CHECK-FILELIST-NOT: emit-interface.swift{{ }}
29-
30-
// RUN: %swiftc_driver -driver-print-jobs -target x86_64-apple-macosx10.9 -experimental-lazy-typecheck %s -emit-module-interface -o %t/foo 2>&1 | %FileCheck -check-prefix=CHECK-LAZY-TYPECHECK %s
31-
32-
// CHECK-LAZY-TYPECHECK: swift{{(-frontend|c)?(\.exe)?"?}} -frontend
33-
// CHECK-LAZY-TYPECHECK-SAME: -experimental-lazy-typecheck
34-
// CHECK-LAZY-TYPECHECK-SAME: emit-interface.swift

test/Inputs/lazy_typecheck.swift

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
// This source file contains intentional type checking errors that should be
2+
// avoided when compiling with -experimental-lazy-typecheck and emitting module
3+
// outputs since all the errors occur in regions of the AST that do not
4+
// need to be type checked in order to emit a module or module interface.
5+
6+
// MARK: - Global functions
7+
8+
public func publicFunc() -> Int {
9+
return true // expected-error {{cannot convert return expression of type 'Bool' to return type 'Int'}}
10+
}
11+
12+
public func publicFuncWithDefaultArg(_ x: Int = 1) -> Int {
13+
return doesNotExist() // expected-error {{cannot find 'doesNotExist' in scope}}
14+
}
15+
16+
package func packageFunc() -> Int {
17+
return false // expected-error {{cannot convert return expression of type 'Bool' to return type 'Int'}}
18+
}
19+
20+
func internalFunc() -> DoesNotExist { // expected-error {{cannot find type 'DoesNotExist' in scope}}
21+
return 1
22+
}
23+
24+
@inlinable func inlinableFunc() -> Int {
25+
return true // expected-error {{cannot convert return expression of type 'Bool' to return type 'Int'}}
26+
}
27+
28+
private func privateFunc() -> DoesNotExist { // expected-error {{cannot find type 'DoesNotExist' in scope}}
29+
return 1
30+
}
31+
32+
public func constrainedGenericPublicFunction<T>(_ t: T) where T: PublicProto {
33+
doesNotExist() // expected-error {{cannot find 'doesNotExist' in scope}}
34+
}
35+
36+
@available(SwiftStdlib 5.1, *)
37+
public func publicFuncWithOpaqueReturnType() -> some PublicProto { // expected-note {{opaque return type declared here}}
38+
return 1 // expected-error {{return type of global function 'publicFuncWithOpaqueReturnType()' requires that 'Int' conform to 'PublicProto'}}
39+
}
40+
41+
@available(SwiftStdlib 5.1, *)
42+
@_alwaysEmitIntoClient public func publicAEICFuncWithOpaqueReturnType() -> some Any {
43+
if #available(macOS 20, *) {
44+
return 3
45+
} else {
46+
return "hi"
47+
}
48+
}
49+
50+
// MARK: - Nominal types
51+
52+
public protocol PublicProto {
53+
func req() -> Int
54+
}
55+
56+
protocol InternalProto {
57+
// FIXME: Serialization causes typechecking of protocols regardless of access level
58+
// func req() -> DoesNotExist
59+
}
60+
61+
public struct PublicStruct {
62+
// FIXME: Test properties
63+
64+
public func publicMethod() -> Int {
65+
return true // expected-error {{cannot convert return expression of type 'Bool' to return type 'Int'}}
66+
}
67+
68+
func internalMethod() -> DoesNotExist { // expected-error {{cannot find type 'DoesNotExist' in scope}}
69+
return 1
70+
}
71+
}
72+
73+
struct InternalStruct: DoesNotExist { // expected-error {{cannot find type 'DoesNotExist' in scope}}
74+
var x: DoesNotExist // expected-error {{cannot find type 'DoesNotExist' in scope}}
75+
76+
func f(_ x: DoesNotExist) {} // expected-error {{cannot find type 'DoesNotExist' in scope}}
77+
}
78+
79+
// FIXME: Test enums
80+
// FIXME: Test conformances
81+
// FIXME: Test global vars
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// RUN: %empty-directory(%t)
2+
// RUN: %target-swift-frontend -swift-version 5 %S/../Inputs/lazy_typecheck.swift -enable-library-evolution -parse-as-library -package-name Package -typecheck -verify
3+
// RUN: %target-swift-frontend -swift-version 5 %S/../Inputs/lazy_typecheck.swift -module-name lazy_typecheck -emit-module -emit-module-path %t/lazy_typecheck.swiftmodule -enable-library-evolution -parse-as-library -package-name Package -experimental-lazy-typecheck -experimental-skip-all-function-bodies -experimental-serialize-external-decls-only
4+
// RUN: %target-swift-frontend -package-name Package -typecheck %s -I %t
5+
6+
import lazy_typecheck
7+
8+
struct ConformsToPublicProto: PublicProto {
9+
func req() -> Int { return 1 }
10+
}
11+
12+
func testGlobalFunctions() {
13+
_ = publicFunc()
14+
_ = publicFuncWithDefaultArg()
15+
_ = packageFunc()
16+
constrainedGenericPublicFunction(ConformsToPublicProto())
17+
if #available(SwiftStdlib 5.1, *) {
18+
_ = publicFuncWithOpaqueReturnType()
19+
_ = publicAEICFuncWithOpaqueReturnType()
20+
}
21+
}
22+
23+
func testPublicStruct(_ s: PublicStruct) {
24+
_ = s.publicMethod()
25+
}

0 commit comments

Comments
 (0)