Skip to content

Commit b904133

Browse files
author
Harlan Haskins
committed
[Modules] Add flag to skip non-inlinable function bodies
This flag, currently staged in as `-experimental-skip-non-inlinable-function-bodies`, will cause the typechecker to skip typechecking bodies of functions that will not be serialized in the resulting `.swiftmodule`. This patch also includes a SIL verifier that ensures that we don’t accidentally include a body that we should have skipped. There is still some work left to make sure the emitted .swiftmodule is exactly the same as what’s emitted without the flag, which is what’s causing the benchmark noise above. I’ll be committing follow-up patches to address those, but for now I’m going to land the implementation behind a flag.
1 parent 7733308 commit b904133

22 files changed

+525
-5
lines changed

include/swift/AST/Decl.h

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5689,7 +5689,8 @@ class AbstractFunctionDecl : public GenericContext, public ValueDecl {
56895689
/// Note that a true return value does not imply that the body was actually
56905690
/// parsed.
56915691
bool hasBody() const {
5692-
return getBodyKind() != BodyKind::None;
5692+
return getBodyKind() != BodyKind::None &&
5693+
getBodyKind() != BodyKind::Skipped;
56935694
}
56945695

56955696
/// Returns true if the text of this function's body can be retrieved either
@@ -5716,14 +5717,22 @@ class AbstractFunctionDecl : public GenericContext, public ValueDecl {
57165717
/// Note that the body was skipped for this function. Function body
57175718
/// cannot be attached after this call.
57185719
void setBodySkipped(SourceRange bodyRange) {
5719-
assert(getBodyKind() == BodyKind::None);
5720+
// FIXME: Remove 'Parsed' from this once we can delay parsing function
5721+
// bodies. Right now -experimental-skip-non-inlinable-function-bodies
5722+
// requires being able to change the state from Parsed to Skipped,
5723+
// because we're still eagerly parsing function bodies.
5724+
assert(getBodyKind() == BodyKind::None ||
5725+
getBodyKind() == BodyKind::Unparsed ||
5726+
getBodyKind() == BodyKind::Parsed);
5727+
assert(bodyRange.isValid());
57205728
BodyRange = bodyRange;
57215729
setBodyKind(BodyKind::Skipped);
57225730
}
57235731

57245732
/// Note that parsing for the body was delayed.
57255733
void setBodyDelayed(SourceRange bodyRange) {
57265734
assert(getBodyKind() == BodyKind::None);
5735+
assert(bodyRange.isValid());
57275736
BodyRange = bodyRange;
57285737
setBodyKind(BodyKind::Unparsed);
57295738
}
@@ -5769,6 +5778,10 @@ class AbstractFunctionDecl : public GenericContext, public ValueDecl {
57695778
return getBodyKind() == BodyKind::TypeChecked;
57705779
}
57715780

5781+
bool isBodySkipped() const {
5782+
return getBodyKind() == BodyKind::Skipped;
5783+
}
5784+
57725785
bool isMemberwiseInitializer() const {
57735786
return getBodyKind() == BodyKind::MemberwiseInitializer;
57745787
}

include/swift/AST/DeclContext.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -409,6 +409,15 @@ class alignas(1 << DeclContextAlignInBits) DeclContext {
409409
const_cast<DeclContext *>(this)->getInnermostDeclarationDeclContext();
410410
}
411411

412+
/// Returns the innermost context that is an AbstractFunctionDecl whose
413+
/// body has been skipped.
414+
LLVM_READONLY
415+
DeclContext *getInnermostSkippedFunctionContext();
416+
const DeclContext *getInnermostSkippedFunctionContext() const {
417+
return
418+
const_cast<DeclContext *>(this)->getInnermostSkippedFunctionContext();
419+
}
420+
412421
/// Returns the semantic parent of this context. A context has a
413422
/// parent if and only if it is not a module context.
414423
DeclContext *getParent() const {

include/swift/AST/DiagnosticsFrontend.def

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,9 @@ ERROR(error_mode_cannot_emit_module_source_info,none,
128128
"this mode does not support emitting module source info files", ())
129129
ERROR(error_mode_cannot_emit_interface,none,
130130
"this mode does not support emitting module interface files", ())
131+
ERROR(cannot_emit_ir_skipping_function_bodies,none,
132+
"-experimental-skip-non-inlinable-function-bodies does not support "
133+
"emitting IR", ())
131134

132135
WARNING(emit_reference_dependencies_without_primary_file,none,
133136
"ignoring -emit-reference-dependencies (requires -primary-file)", ())

include/swift/AST/SILOptions.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,9 @@ class SILOptions {
7070
/// Whether to stop the optimization pipeline after serializing SIL.
7171
bool StopOptimizationAfterSerialization = false;
7272

73+
/// Whether to skip emitting non-inlinable function bodies.
74+
bool SkipNonInlinableFunctionBodies = false;
75+
7376
/// Optimization mode being used.
7477
OptimizationMode OptMode = OptimizationMode::NotSet;
7578

include/swift/Frontend/FrontendOptions.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,8 @@ class FrontendOptions {
180180
/// \sa swift::SharedTimer
181181
bool DebugTimeCompilation = false;
182182

183+
bool SkipNonInlinableFunctionBodies = false;
184+
183185
/// The path to which we should output statistics files.
184186
std::string StatsOutputDir;
185187

@@ -339,6 +341,7 @@ class FrontendOptions {
339341

340342
public:
341343
static bool doesActionGenerateSIL(ActionType);
344+
static bool doesActionGenerateIR(ActionType);
342345
static bool doesActionProduceOutput(ActionType);
343346
static bool doesActionProduceTextualOutput(ActionType);
344347
static bool needsProperModuleName(ActionType);

include/swift/Option/Options.td

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,10 @@ def stats_output_dir: Separate<["-"], "stats-output-dir">,
254254
def trace_stats_events: Flag<["-"], "trace-stats-events">,
255255
Flags<[FrontendOption, HelpHidden]>,
256256
HelpText<"Trace changes to stats in -stats-output-dir">;
257+
def experimental_skip_non_inlinable_function_bodies:
258+
Flag<["-"], "experimental-skip-non-inlinable-function-bodies">,
259+
Flags<[FrontendOption, HelpHidden]>,
260+
HelpText<"Skip type-checking and SIL generation for non-inlinable function bodies">;
257261
def profile_stats_events: Flag<["-"], "profile-stats-events">,
258262
Flags<[FrontendOption, HelpHidden]>,
259263
HelpText<"Profile changes to stats in -stats-output-dir">;

include/swift/SILOptimizer/PassManager/Passes.def

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,9 @@ PASS(SimplifyUnreachableContainingBlocks, "simplify-unreachable-containing-block
311311
"Utility pass. Removes all non-term insts from blocks with unreachable terms")
312312
PASS(SerializeSILPass, "serialize-sil",
313313
"Utility pass. Serializes the current SILModule")
314+
PASS(NonInlinableFunctionSkippingChecker, "check-non-inlinable-function-skipping",
315+
"Utility pass to ensure -experimental-skip-non-inlinable-function-bodies "
316+
"skips everything it should")
314317
PASS(YieldOnceCheck, "yield-once-check",
315318
"Check correct usage of yields in yield-once coroutines")
316319
PASS(OSLogOptimization, "os-log-optimization", "Optimize os log calls")

include/swift/Subsystems.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,11 @@ namespace swift {
187187
/// If set, dumps wall time taken to type check each expression to
188188
/// llvm::errs().
189189
DebugTimeExpressions = 1 << 3,
190+
191+
/// If set, the typechecker will skip typechecking non-inlinable function
192+
/// bodies. Set this if you're trying to quickly emit a module or module
193+
/// interface without a full compilation.
194+
SkipNonInlinableFunctionBodies = 1 << 4,
190195
};
191196

192197
/// Once parsing and name-binding are complete, this walks the AST to resolve

lib/AST/DeclContext.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,17 @@ Decl *DeclContext::getInnermostDeclarationDeclContext() {
231231
return nullptr;
232232
}
233233

234+
DeclContext *DeclContext::getInnermostSkippedFunctionContext() {
235+
auto dc = this;
236+
do {
237+
if (auto afd = dyn_cast<AbstractFunctionDecl>(dc))
238+
if (afd->isBodySkipped())
239+
return afd;
240+
} while ((dc = dc->getParent()));
241+
242+
return nullptr;
243+
}
244+
234245
DeclContext *DeclContext::getParentForLookup() const {
235246
if (isa<ProtocolDecl>(this) || isa<ExtensionDecl>(this)) {
236247
// If we are inside a protocol or an extension, skip directly

lib/Frontend/ArgsToFrontendOptionsConverter.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,12 @@ bool ArgsToFrontendOptionsConverter::convert(
161161
if (checkUnusedSupplementaryOutputPaths())
162162
return true;
163163

164+
if (FrontendOptions::doesActionGenerateIR(Opts.RequestedAction)
165+
&& Opts.SkipNonInlinableFunctionBodies) {
166+
Diags.diagnose(SourceLoc(), diag::cannot_emit_ir_skipping_function_bodies);
167+
return true;
168+
}
169+
164170
if (const Arg *A = Args.getLastArg(OPT_module_link_name))
165171
Opts.ModuleLinkName = A->getValue();
166172

@@ -219,6 +225,8 @@ void ArgsToFrontendOptionsConverter::computeDebugTimeOptions() {
219225
Opts.DebugTimeExpressionTypeChecking |=
220226
Args.hasArg(OPT_debug_time_expression_type_checking);
221227
Opts.DebugTimeCompilation |= Args.hasArg(OPT_debug_time_compilation);
228+
Opts.SkipNonInlinableFunctionBodies |=
229+
Args.hasArg(OPT_experimental_skip_non_inlinable_function_bodies);
222230
if (const Arg *A = Args.getLastArg(OPT_stats_output_dir)) {
223231
Opts.StatsOutputDir = A->getValue();
224232
if (Args.getLastArg(OPT_trace_stats_events)) {

lib/Frontend/CompilerInvocation.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -763,6 +763,9 @@ static bool ParseSILArgs(SILOptions &Opts, ArgList &Args,
763763
if (Args.hasArg(OPT_sil_merge_partial_modules))
764764
Opts.MergePartialModules = true;
765765

766+
if (Args.hasArg(OPT_experimental_skip_non_inlinable_function_bodies))
767+
Opts.SkipNonInlinableFunctionBodies = true;
768+
766769
// Parse the optimization level.
767770
// Default to Onone settings if no option is passed.
768771
Opts.OptMode = OptimizationMode::NoOptimization;

lib/Frontend/Frontend.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -886,6 +886,9 @@ OptionSet<TypeCheckingFlags> CompilerInstance::computeTypeCheckingOptions() {
886886
if (options.DebugTimeExpressionTypeChecking) {
887887
TypeCheckOptions |= TypeCheckingFlags::DebugTimeExpressions;
888888
}
889+
if (options.SkipNonInlinableFunctionBodies) {
890+
TypeCheckOptions |= TypeCheckingFlags::SkipNonInlinableFunctionBodies;
891+
}
889892
return TypeCheckOptions;
890893
}
891894

lib/Frontend/FrontendOptions.cpp

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -520,6 +520,41 @@ bool FrontendOptions::doesActionGenerateSIL(ActionType action) {
520520
llvm_unreachable("unhandled action");
521521
}
522522

523+
bool FrontendOptions::doesActionGenerateIR(ActionType action) {
524+
switch (action) {
525+
case ActionType::NoneAction:
526+
case ActionType::Parse:
527+
case ActionType::DumpParse:
528+
case ActionType::DumpInterfaceHash:
529+
case ActionType::DumpAST:
530+
case ActionType::EmitSyntax:
531+
case ActionType::PrintAST:
532+
case ActionType::DumpScopeMaps:
533+
case ActionType::DumpTypeRefinementContexts:
534+
case ActionType::DumpTypeInfo:
535+
case ActionType::CompileModuleFromInterface:
536+
case ActionType::Typecheck:
537+
case ActionType::ResolveImports:
538+
case ActionType::MergeModules:
539+
case ActionType::EmitModuleOnly:
540+
case ActionType::EmitPCH:
541+
case ActionType::EmitSILGen:
542+
case ActionType::EmitSIL:
543+
case ActionType::EmitSIBGen:
544+
case ActionType::EmitSIB:
545+
case ActionType::EmitImportedModules:
546+
return false;
547+
case ActionType::Immediate:
548+
case ActionType::REPL:
549+
case ActionType::EmitIR:
550+
case ActionType::EmitBC:
551+
case ActionType::EmitAssembly:
552+
case ActionType::EmitObject:
553+
return true;
554+
}
555+
llvm_unreachable("unhandled action");
556+
}
557+
523558

524559
const PrimarySpecificPaths &
525560
FrontendOptions::getPrimarySpecificPathsForAtMostOnePrimary() const {

lib/SILGen/SILGen.cpp

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1669,9 +1669,13 @@ void SILGenModule::emitSourceFile(SourceFile *sf) {
16691669
visit(D);
16701670
}
16711671

1672-
for (Decl *D : sf->LocalTypeDecls) {
1673-
FrontendStatsTracer StatsTracer(getASTContext().Stats, "SILgen-tydecl", D);
1674-
visit(D);
1672+
for (TypeDecl *TD : sf->LocalTypeDecls) {
1673+
FrontendStatsTracer StatsTracer(getASTContext().Stats, "SILgen-tydecl", TD);
1674+
// FIXME: Delayed parsing would prevent these types from being added to the
1675+
// module in the first place.
1676+
if (TD->getDeclContext()->getInnermostSkippedFunctionContext())
1677+
continue;
1678+
visit(TD);
16751679
}
16761680
}
16771681

lib/SILOptimizer/PassManager/PassPipeline.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,14 @@ static void addMandatoryOptPipeline(SILPassPipelinePlan &P) {
102102
// there.
103103
const auto &Options = P.getOptions();
104104
P.addClosureLifetimeFixup();
105+
106+
#ifndef NDEBUG
107+
// Add a verification pass to check our work when skipping non-inlinable
108+
// function bodies.
109+
if (Options.SkipNonInlinableFunctionBodies)
110+
P.addNonInlinableFunctionSkippingChecker();
111+
#endif
112+
105113
if (Options.shouldOptimize()) {
106114
P.addSemanticARCOpts();
107115
P.addDestroyHoisting();

lib/SILOptimizer/UtilityPasses/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ silopt_register_sources(
77
BugReducerTester.cpp
88
CFGPrinter.cpp
99
CallerAnalysisPrinter.cpp
10+
NonInlinableFunctionSkippingChecker.cpp
1011
ComputeDominanceInfo.cpp
1112
ComputeLoopInfo.cpp
1213
ConstantEvaluatorTester.cpp
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
//===------------- NonInlinableFunctionSkippingChecker.cpp ----------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
#include "swift/Basic/LLVM.h"
14+
#include "swift/SIL/SILFunction.h"
15+
#include "swift/SIL/SILInstruction.h"
16+
#include "swift/SIL/SILModule.h"
17+
#include "swift/SILOptimizer/PassManager/Transforms.h"
18+
#include "llvm/ADT/StringRef.h"
19+
#include "llvm/Support/raw_ostream.h"
20+
21+
using namespace swift;
22+
23+
/// Determines whether we should have skipped SILGenning a function that
24+
/// appears in a SILModule. This only applies when
25+
/// \c -experimental-skip-non-inlinable-function-bodies is enabled.
26+
static bool shouldHaveSkippedFunction(const SILFunction &F) {
27+
assert(F.getModule().getOptions().SkipNonInlinableFunctionBodies &&
28+
"this check only makes sense if we're skipping function bodies");
29+
30+
// First, we only care about functions that haven't been marked serialized.
31+
// If they've been marked serialized, they will end up in the final module
32+
// and we needed to SILGen them.
33+
if (F.isSerialized())
34+
return false;
35+
36+
// Next, we're looking for functions that shouldn't have a body, but do. If
37+
// the function doesn't have a body (i.e. it's an external declaration), we
38+
// skipped it successfully.
39+
if (F.isExternalDeclaration())
40+
return false;
41+
42+
// Thunks and specializations are automatically synthesized, so it's fine that
43+
// they end up in the module.
44+
if (F.isThunk() || F.isSpecialization())
45+
return false;
46+
47+
// FIXME: We can probably skip property initializers, too.
48+
auto func = F.getLocation().getAsASTNode<AbstractFunctionDecl>();
49+
if (!func)
50+
return false;
51+
52+
// If a body is synthesized/implicit, it shouldn't be skipped.
53+
if (func->isImplicit())
54+
return false;
55+
56+
// Local function bodies in inlinable code are okay to show up in the module.
57+
if (func->getDeclContext()->isLocalContext())
58+
return false;
59+
60+
// FIXME: Identify __ivar_destroyer, __allocating_init, and
61+
// __deallocating_deinit, which have no special marking, are always
62+
// emitted, and do have a source location with the original decl
63+
// attached.
64+
if (isa<DestructorDecl>(func) || isa<ConstructorDecl>(func))
65+
return false;
66+
67+
// If none of those conditions trip, then this is something that _should_
68+
// be serialized in the module even when we're skipping non-inlinable
69+
// function bodies.
70+
return true;
71+
}
72+
73+
namespace {
74+
75+
/// This is a verification utility pass that's meant to be used with
76+
/// \c -experimental-skip-non-inlinable-function-bodies. It checks all the
77+
/// functions in a module and ensures that, if the function was written in
78+
/// source and not serialized, then it wasn't even SILGen'd.
79+
class NonInlinableFunctionSkippingChecker : public SILModuleTransform {
80+
void run() override {
81+
// Skip this if we're not skipping function bodies
82+
if (!getModule()->getOptions().SkipNonInlinableFunctionBodies)
83+
return;
84+
85+
// Skip this verification for SwiftOnoneSupport
86+
if (getModule()->isOptimizedOnoneSupportModule())
87+
return;
88+
89+
for (auto &F : *getModule()) {
90+
if (!shouldHaveSkippedFunction(F))
91+
continue;
92+
93+
llvm::dbgs() << "Function serialized that should have been skipped!\n";
94+
F.getLocation().dump(F.getModule().getSourceManager());
95+
llvm::dbgs() << "\n";
96+
F.dump();
97+
abort();
98+
}
99+
}
100+
101+
};
102+
103+
} // end anonymous namespace
104+
105+
SILTransform *swift::createNonInlinableFunctionSkippingChecker() {
106+
return new NonInlinableFunctionSkippingChecker();
107+
}

0 commit comments

Comments
 (0)