Skip to content

[Frontend] Add experimental flag to skip non-inlinable function bodies #20420

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Sep 26, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 15 additions & 2 deletions include/swift/AST/Decl.h
Original file line number Diff line number Diff line change
Expand Up @@ -5689,7 +5689,8 @@ class AbstractFunctionDecl : public GenericContext, public ValueDecl {
/// Note that a true return value does not imply that the body was actually
/// parsed.
bool hasBody() const {
return getBodyKind() != BodyKind::None;
return getBodyKind() != BodyKind::None &&
getBodyKind() != BodyKind::Skipped;
}

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

/// Note that parsing for the body was delayed.
void setBodyDelayed(SourceRange bodyRange) {
assert(getBodyKind() == BodyKind::None);
assert(bodyRange.isValid());
BodyRange = bodyRange;
setBodyKind(BodyKind::Unparsed);
}
Expand Down Expand Up @@ -5769,6 +5778,10 @@ class AbstractFunctionDecl : public GenericContext, public ValueDecl {
return getBodyKind() == BodyKind::TypeChecked;
}

bool isBodySkipped() const {
return getBodyKind() == BodyKind::Skipped;
}

bool isMemberwiseInitializer() const {
return getBodyKind() == BodyKind::MemberwiseInitializer;
}
Expand Down
9 changes: 9 additions & 0 deletions include/swift/AST/DeclContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,15 @@ class alignas(1 << DeclContextAlignInBits) DeclContext {
const_cast<DeclContext *>(this)->getInnermostDeclarationDeclContext();
}

/// Returns the innermost context that is an AbstractFunctionDecl whose
/// body has been skipped.
LLVM_READONLY
DeclContext *getInnermostSkippedFunctionContext();
const DeclContext *getInnermostSkippedFunctionContext() const {
return
const_cast<DeclContext *>(this)->getInnermostSkippedFunctionContext();
}

/// Returns the semantic parent of this context. A context has a
/// parent if and only if it is not a module context.
DeclContext *getParent() const {
Expand Down
3 changes: 3 additions & 0 deletions include/swift/AST/DiagnosticsFrontend.def
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,9 @@ ERROR(error_mode_cannot_emit_module_source_info,none,
"this mode does not support emitting module source info files", ())
ERROR(error_mode_cannot_emit_interface,none,
"this mode does not support emitting module interface files", ())
ERROR(cannot_emit_ir_skipping_function_bodies,none,
"-experimental-skip-non-inlinable-function-bodies does not support "
"emitting IR", ())

WARNING(emit_reference_dependencies_without_primary_file,none,
"ignoring -emit-reference-dependencies (requires -primary-file)", ())
Expand Down
3 changes: 3 additions & 0 deletions include/swift/AST/SILOptions.h
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@ class SILOptions {
/// Whether to stop the optimization pipeline after serializing SIL.
bool StopOptimizationAfterSerialization = false;

/// Whether to skip emitting non-inlinable function bodies.
bool SkipNonInlinableFunctionBodies = false;

/// Optimization mode being used.
OptimizationMode OptMode = OptimizationMode::NotSet;

Expand Down
3 changes: 3 additions & 0 deletions include/swift/Frontend/FrontendOptions.h
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,8 @@ class FrontendOptions {
/// \sa swift::SharedTimer
bool DebugTimeCompilation = false;

bool SkipNonInlinableFunctionBodies = false;

/// The path to which we should output statistics files.
std::string StatsOutputDir;

Expand Down Expand Up @@ -339,6 +341,7 @@ class FrontendOptions {

public:
static bool doesActionGenerateSIL(ActionType);
static bool doesActionGenerateIR(ActionType);
static bool doesActionProduceOutput(ActionType);
static bool doesActionProduceTextualOutput(ActionType);
static bool needsProperModuleName(ActionType);
Expand Down
4 changes: 4 additions & 0 deletions include/swift/Option/Options.td
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,10 @@ def stats_output_dir: Separate<["-"], "stats-output-dir">,
def trace_stats_events: Flag<["-"], "trace-stats-events">,
Flags<[FrontendOption, HelpHidden]>,
HelpText<"Trace changes to stats in -stats-output-dir">;
def experimental_skip_non_inlinable_function_bodies:
Flag<["-"], "experimental-skip-non-inlinable-function-bodies">,
Flags<[FrontendOption, HelpHidden]>,
HelpText<"Skip type-checking and SIL generation for non-inlinable function bodies">;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick: why is this in the middle of all the stats options?

(Also, why is it a Driver option again?)

def profile_stats_events: Flag<["-"], "profile-stats-events">,
Flags<[FrontendOption, HelpHidden]>,
HelpText<"Profile changes to stats in -stats-output-dir">;
Expand Down
3 changes: 3 additions & 0 deletions include/swift/SILOptimizer/PassManager/Passes.def
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,9 @@ PASS(SimplifyUnreachableContainingBlocks, "simplify-unreachable-containing-block
"Utility pass. Removes all non-term insts from blocks with unreachable terms")
PASS(SerializeSILPass, "serialize-sil",
"Utility pass. Serializes the current SILModule")
PASS(NonInlinableFunctionSkippingChecker, "check-non-inlinable-function-skipping",
"Utility pass to ensure -experimental-skip-non-inlinable-function-bodies "
"skips everything it should")
PASS(YieldOnceCheck, "yield-once-check",
"Check correct usage of yields in yield-once coroutines")
PASS(OSLogOptimization, "os-log-optimization", "Optimize os log calls")
Expand Down
5 changes: 5 additions & 0 deletions include/swift/Subsystems.h
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,11 @@ namespace swift {
/// If set, dumps wall time taken to type check each expression to
/// llvm::errs().
DebugTimeExpressions = 1 << 3,

/// If set, the typechecker will skip typechecking non-inlinable function
/// bodies. Set this if you're trying to quickly emit a module or module
/// interface without a full compilation.
SkipNonInlinableFunctionBodies = 1 << 4,
};

/// Once parsing and name-binding are complete, this walks the AST to resolve
Expand Down
11 changes: 11 additions & 0 deletions lib/AST/DeclContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,17 @@ Decl *DeclContext::getInnermostDeclarationDeclContext() {
return nullptr;
}

DeclContext *DeclContext::getInnermostSkippedFunctionContext() {
auto dc = this;
do {
if (auto afd = dyn_cast<AbstractFunctionDecl>(dc))
if (afd->isBodySkipped())
return afd;
} while ((dc = dc->getParent()));

return nullptr;
}

DeclContext *DeclContext::getParentForLookup() const {
if (isa<ProtocolDecl>(this) || isa<ExtensionDecl>(this)) {
// If we are inside a protocol or an extension, skip directly
Expand Down
8 changes: 8 additions & 0 deletions lib/Frontend/ArgsToFrontendOptionsConverter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,12 @@ bool ArgsToFrontendOptionsConverter::convert(
if (checkUnusedSupplementaryOutputPaths())
return true;

if (FrontendOptions::doesActionGenerateIR(Opts.RequestedAction)
&& Opts.SkipNonInlinableFunctionBodies) {
Diags.diagnose(SourceLoc(), diag::cannot_emit_ir_skipping_function_bodies);
return true;
}

if (const Arg *A = Args.getLastArg(OPT_module_link_name))
Opts.ModuleLinkName = A->getValue();

Expand Down Expand Up @@ -219,6 +225,8 @@ void ArgsToFrontendOptionsConverter::computeDebugTimeOptions() {
Opts.DebugTimeExpressionTypeChecking |=
Args.hasArg(OPT_debug_time_expression_type_checking);
Opts.DebugTimeCompilation |= Args.hasArg(OPT_debug_time_compilation);
Opts.SkipNonInlinableFunctionBodies |=
Args.hasArg(OPT_experimental_skip_non_inlinable_function_bodies);
if (const Arg *A = Args.getLastArg(OPT_stats_output_dir)) {
Opts.StatsOutputDir = A->getValue();
if (Args.getLastArg(OPT_trace_stats_events)) {
Expand Down
3 changes: 3 additions & 0 deletions lib/Frontend/CompilerInvocation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -763,6 +763,9 @@ static bool ParseSILArgs(SILOptions &Opts, ArgList &Args,
if (Args.hasArg(OPT_sil_merge_partial_modules))
Opts.MergePartialModules = true;

if (Args.hasArg(OPT_experimental_skip_non_inlinable_function_bodies))
Opts.SkipNonInlinableFunctionBodies = true;

// Parse the optimization level.
// Default to Onone settings if no option is passed.
Opts.OptMode = OptimizationMode::NoOptimization;
Expand Down
3 changes: 3 additions & 0 deletions lib/Frontend/Frontend.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -886,6 +886,9 @@ OptionSet<TypeCheckingFlags> CompilerInstance::computeTypeCheckingOptions() {
if (options.DebugTimeExpressionTypeChecking) {
TypeCheckOptions |= TypeCheckingFlags::DebugTimeExpressions;
}
if (options.SkipNonInlinableFunctionBodies) {
TypeCheckOptions |= TypeCheckingFlags::SkipNonInlinableFunctionBodies;
}
return TypeCheckOptions;
}

Expand Down
35 changes: 35 additions & 0 deletions lib/Frontend/FrontendOptions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -520,6 +520,41 @@ bool FrontendOptions::doesActionGenerateSIL(ActionType action) {
llvm_unreachable("unhandled action");
}

bool FrontendOptions::doesActionGenerateIR(ActionType action) {
switch (action) {
case ActionType::NoneAction:
case ActionType::Parse:
case ActionType::DumpParse:
case ActionType::DumpInterfaceHash:
case ActionType::DumpAST:
case ActionType::EmitSyntax:
case ActionType::PrintAST:
case ActionType::DumpScopeMaps:
case ActionType::DumpTypeRefinementContexts:
case ActionType::DumpTypeInfo:
case ActionType::CompileModuleFromInterface:
case ActionType::Typecheck:
case ActionType::ResolveImports:
case ActionType::MergeModules:
case ActionType::EmitModuleOnly:
case ActionType::EmitPCH:
case ActionType::EmitSILGen:
case ActionType::EmitSIL:
case ActionType::EmitSIBGen:
case ActionType::EmitSIB:
case ActionType::EmitImportedModules:
return false;
case ActionType::Immediate:
case ActionType::REPL:
case ActionType::EmitIR:
case ActionType::EmitBC:
case ActionType::EmitAssembly:
case ActionType::EmitObject:
return true;
}
llvm_unreachable("unhandled action");
}


const PrimarySpecificPaths &
FrontendOptions::getPrimarySpecificPathsForAtMostOnePrimary() const {
Expand Down
10 changes: 7 additions & 3 deletions lib/SILGen/SILGen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1669,9 +1669,13 @@ void SILGenModule::emitSourceFile(SourceFile *sf) {
visit(D);
}

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

Expand Down
8 changes: 8 additions & 0 deletions lib/SILOptimizer/PassManager/PassPipeline.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,14 @@ static void addMandatoryOptPipeline(SILPassPipelinePlan &P) {
// there.
const auto &Options = P.getOptions();
P.addClosureLifetimeFixup();

#ifndef NDEBUG
// Add a verification pass to check our work when skipping non-inlinable
// function bodies.
if (Options.SkipNonInlinableFunctionBodies)
P.addNonInlinableFunctionSkippingChecker();
#endif

if (Options.shouldOptimize()) {
P.addSemanticARCOpts();
P.addDestroyHoisting();
Expand Down
1 change: 1 addition & 0 deletions lib/SILOptimizer/UtilityPasses/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ silopt_register_sources(
BugReducerTester.cpp
CFGPrinter.cpp
CallerAnalysisPrinter.cpp
NonInlinableFunctionSkippingChecker.cpp
ComputeDominanceInfo.cpp
ComputeLoopInfo.cpp
ConstantEvaluatorTester.cpp
Expand Down
107 changes: 107 additions & 0 deletions lib/SILOptimizer/UtilityPasses/NonInlinableFunctionSkippingChecker.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
//===------------- NonInlinableFunctionSkippingChecker.cpp ----------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

#include "swift/Basic/LLVM.h"
#include "swift/SIL/SILFunction.h"
#include "swift/SIL/SILInstruction.h"
#include "swift/SIL/SILModule.h"
#include "swift/SILOptimizer/PassManager/Transforms.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/raw_ostream.h"

using namespace swift;

/// Determines whether we should have skipped SILGenning a function that
/// appears in a SILModule. This only applies when
/// \c -experimental-skip-non-inlinable-function-bodies is enabled.
static bool shouldHaveSkippedFunction(const SILFunction &F) {
assert(F.getModule().getOptions().SkipNonInlinableFunctionBodies &&
"this check only makes sense if we're skipping function bodies");

// First, we only care about functions that haven't been marked serialized.
// If they've been marked serialized, they will end up in the final module
// and we needed to SILGen them.
if (F.isSerialized())
return false;

// Next, we're looking for functions that shouldn't have a body, but do. If
// the function doesn't have a body (i.e. it's an external declaration), we
// skipped it successfully.
if (F.isExternalDeclaration())
return false;

// Thunks and specializations are automatically synthesized, so it's fine that
// they end up in the module.
if (F.isThunk() || F.isSpecialization())
return false;

// FIXME: We can probably skip property initializers, too.
auto func = F.getLocation().getAsASTNode<AbstractFunctionDecl>();
if (!func)
return false;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, this scares me. All sorts of synthesized things will slip through here too.


// If a body is synthesized/implicit, it shouldn't be skipped.
if (func->isImplicit())
return false;

// Local function bodies in inlinable code are okay to show up in the module.
if (func->getDeclContext()->isLocalContext())
return false;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Worth checking that the enclosing context is in fact inlinable.


// FIXME: Identify __ivar_destroyer, __allocating_init, and
// __deallocating_deinit, which have no special marking, are always
// emitted, and do have a source location with the original decl
// attached.
if (isa<DestructorDecl>(func) || isa<ConstructorDecl>(func))
return false;

// If none of those conditions trip, then this is something that _should_
// be serialized in the module even when we're skipping non-inlinable
// function bodies.
return true;
}

namespace {

/// This is a verification utility pass that's meant to be used with
/// \c -experimental-skip-non-inlinable-function-bodies. It checks all the
/// functions in a module and ensures that, if the function was written in
/// source and not serialized, then it wasn't even SILGen'd.
class NonInlinableFunctionSkippingChecker : public SILModuleTransform {
void run() override {
// Skip this if we're not skipping function bodies
if (!getModule()->getOptions().SkipNonInlinableFunctionBodies)
return;

// Skip this verification for SwiftOnoneSupport
if (getModule()->isOptimizedOnoneSupportModule())
return;

for (auto &F : *getModule()) {
if (!shouldHaveSkippedFunction(F))
continue;

llvm::dbgs() << "Function serialized that should have been skipped!\n";
F.getLocation().dump(F.getModule().getSourceManager());
llvm::dbgs() << "\n";
F.dump();
abort();
}
}

};

} // end anonymous namespace

SILTransform *swift::createNonInlinableFunctionSkippingChecker() {
return new NonInlinableFunctionSkippingChecker();
}
Loading