Skip to content

Commit c22e046

Browse files
committed
AST/SILGen: Requestify function body skipping.
Function bodies are skipped during typechecking when one of the -experimental-skip-*-function-bodies flags is passed to the frontend. This was implemented by setting the "body kind" of an `AbstractFunctionDecl` during decl checking in `TypeCheckDeclPrimary`. This approach had a couple of issues: - It is incompatible with skipping function bodies during lazy typechecking, since the skipping is only evaluated during a phase of eager typechecking. - It prevents skipped function bodies from being parsed on-demand ("skipped" is a state that is distinct from "parsed", when they ought to be orthogonal). This needlessly prevented complete module interfaces from being emitted with -experimental-skip-all-function-bodies. Storing the skipped status of a function separately from body kind and requestifying the determination of whether to skip a function solves these problems. Resolves rdar://116020403
1 parent d047419 commit c22e046

16 files changed

+214
-116
lines changed

include/swift/AST/Decl.h

Lines changed: 26 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -450,10 +450,13 @@ class alignas(1 << DeclAlignInBits) Decl : public ASTAllocated<Decl> {
450450
SWIFT_INLINE_BITFIELD(SubscriptDecl, VarDecl, 2,
451451
StaticSpelling : 2
452452
);
453-
SWIFT_INLINE_BITFIELD(AbstractFunctionDecl, ValueDecl, 3+2+8+1+1+1+1+1+1+1,
453+
SWIFT_INLINE_BITFIELD(AbstractFunctionDecl, ValueDecl, 3+2+2+8+1+1+1+1+1+1+1,
454454
/// \see AbstractFunctionDecl::BodyKind
455455
BodyKind : 3,
456456

457+
/// \see AbstractFunctionDecl::BodySkippedStatus
458+
BodySkippedStatus : 2,
459+
457460
/// \see AbstractFunctionDecl::SILSynthesizeKind
458461
SILSynthesizeKind : 2,
459462

@@ -6856,9 +6859,6 @@ class AbstractFunctionDecl : public GenericContext, public ValueDecl {
68566859
/// Function body is parsed and available as an AST subtree.
68576860
Parsed,
68586861

6859-
/// Function body is not available, although it was written in the source.
6860-
Skipped,
6861-
68626862
/// Function body will be synthesized on demand.
68636863
Synthesize,
68646864

@@ -6874,6 +6874,14 @@ class AbstractFunctionDecl : public GenericContext, public ValueDecl {
68746874
// This enum currently needs to fit in a 3-bit bitfield.
68756875
};
68766876

6877+
enum class BodySkippedStatus {
6878+
Unknown,
6879+
Skipped,
6880+
NotSkipped,
6881+
6882+
// This enum needs to fit in a 2-bit bitfield.
6883+
};
6884+
68776885
BodyKind getBodyKind() const {
68786886
return BodyKind(Bits.AbstractFunctionDecl.BodyKind);
68796887
}
@@ -6925,13 +6933,13 @@ class AbstractFunctionDecl : public GenericContext, public ValueDecl {
69256933

69266934
/// The location of the function body when the body is delayed or skipped.
69276935
///
6928-
/// This enum member is active if getBodyKind() is BodyKind::Unparsed or
6929-
/// BodyKind::Skipped.
6936+
/// This enum member is active if getBodyKind() is BodyKind::Unparsed.
69306937
SourceRange BodyRange;
69316938
};
69326939

69336940
friend class ParseAbstractFunctionBodyRequest;
69346941
friend class TypeCheckFunctionBodyRequest;
6942+
friend class IsFunctionBodySkippedRequest;
69356943

69366944
CaptureInfo Captures;
69376945

@@ -6969,6 +6977,14 @@ class AbstractFunctionDecl : public GenericContext, public ValueDecl {
69696977
Bits.AbstractFunctionDecl.BodyKind = unsigned(K);
69706978
}
69716979

6980+
BodySkippedStatus getBodySkippedStatus() const {
6981+
return BodySkippedStatus(Bits.AbstractFunctionDecl.BodySkippedStatus);
6982+
}
6983+
6984+
void setBodySkippedStatus(BodySkippedStatus status) {
6985+
Bits.AbstractFunctionDecl.BodySkippedStatus = unsigned(status);
6986+
}
6987+
69726988
void setSILSynthesizeKind(SILSynthesizeKind K) {
69736989
Bits.AbstractFunctionDecl.SILSynthesizeKind = unsigned(K);
69746990
}
@@ -7087,10 +7103,7 @@ class AbstractFunctionDecl : public GenericContext, public ValueDecl {
70877103
///
70887104
/// Note that a true return value does not imply that the body was actually
70897105
/// parsed.
7090-
bool hasBody() const {
7091-
return getBodyKind() != BodyKind::None &&
7092-
getBodyKind() != BodyKind::Skipped;
7093-
}
7106+
bool hasBody() const { return getBodyKind() != BodyKind::None; }
70947107

70957108
/// Returns true if the text of this function's body can be retrieved either
70967109
/// by extracting the text from the source buffer or reading the inlinable
@@ -7112,21 +7125,6 @@ class AbstractFunctionDecl : public GenericContext, public ValueDecl {
71127125
/// Set a new body for the function.
71137126
void setBody(BraceStmt *S, BodyKind NewBodyKind);
71147127

7115-
/// Note that the body was skipped for this function. Function body
7116-
/// cannot be attached after this call.
7117-
void setBodySkipped(SourceRange bodyRange) {
7118-
// FIXME: Remove 'Parsed' from this list once we can always delay
7119-
// parsing bodies. The -experimental-skip-*-function-bodies options
7120-
// do currently skip parsing, unless disabled through other means in
7121-
// SourceFile::hasDelayedBodyParsing.
7122-
assert(getBodyKind() == BodyKind::None ||
7123-
getBodyKind() == BodyKind::Unparsed ||
7124-
getBodyKind() == BodyKind::Parsed);
7125-
assert(bodyRange.isValid());
7126-
BodyRange = bodyRange;
7127-
setBodyKind(BodyKind::Skipped);
7128-
}
7129-
71307128
/// Note that parsing for the body was delayed.
71317129
void setBodyDelayed(SourceRange bodyRange) {
71327130
assert(getBodyKind() == BodyKind::None);
@@ -7204,9 +7202,9 @@ class AbstractFunctionDecl : public GenericContext, public ValueDecl {
72047202
return getBodyKind() == BodyKind::SILSynthesize;
72057203
}
72067204

7207-
bool isBodySkipped() const {
7208-
return getBodyKind() == BodyKind::Skipped;
7209-
}
7205+
/// Indicates whether the body of this function is skipped during
7206+
/// typechecking.
7207+
bool isBodySkipped() const;
72107208

72117209
bool isMemberwiseInitializer() const {
72127210
return getBodyKind() == BodyKind::SILSynthesize

include/swift/AST/TypeCheckRequests.h

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4423,6 +4423,25 @@ class SerializeAttrGenericSignatureRequest
44234423
void cacheResult(GenericSignature signature) const;
44244424
};
44254425

4426+
class IsFunctionBodySkippedRequest
4427+
: public SimpleRequest<IsFunctionBodySkippedRequest,
4428+
bool(const AbstractFunctionDecl *),
4429+
RequestFlags::SeparatelyCached> {
4430+
public:
4431+
using SimpleRequest::SimpleRequest;
4432+
4433+
private:
4434+
friend SimpleRequest;
4435+
4436+
bool evaluate(Evaluator &evaluator, const AbstractFunctionDecl *) const;
4437+
4438+
public:
4439+
// Separate caching.
4440+
bool isCached() const { return true; }
4441+
llvm::Optional<bool> getCachedResult() const;
4442+
void cacheResult(bool isSkipped) const;
4443+
};
4444+
44264445
#define SWIFT_TYPEID_ZONE TypeChecker
44274446
#define SWIFT_TYPEID_HEADER "swift/AST/TypeCheckerTypeIDZone.def"
44284447
#include "swift/Basic/DefineTypeIDZone.h"

include/swift/AST/TypeCheckerTypeIDZone.def

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -503,3 +503,6 @@ SWIFT_REQUEST(TypeChecker, ExpandChildTypeRefinementContextsRequest,
503503
SWIFT_REQUEST(TypeChecker, SerializeAttrGenericSignatureRequest,
504504
GenericSignature(Decl *, SpecializeAttr *),
505505
SeparatelyCached, NoLocationInfo)
506+
SWIFT_REQUEST(TypeChecker, IsFunctionBodySkippedRequest,
507+
bool(const AbstractFunctionDecl *),
508+
SeparatelyCached, NoLocationInfo)

lib/AST/ASTVerifier.cpp

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -496,7 +496,6 @@ class Verifier : public ASTWalker {
496496
switch (afd->getBodyKind()) {
497497
case AbstractFunctionDecl::BodyKind::None:
498498
case AbstractFunctionDecl::BodyKind::TypeChecked:
499-
case AbstractFunctionDecl::BodyKind::Skipped:
500499
case AbstractFunctionDecl::BodyKind::SILSynthesize:
501500
case AbstractFunctionDecl::BodyKind::Deserialized:
502501
return true;

lib/AST/Decl.cpp

Lines changed: 81 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8934,9 +8934,6 @@ BraceStmt *AbstractFunctionDecl::getTypecheckedBody() const {
89348934
}
89358935

89368936
void AbstractFunctionDecl::setBody(BraceStmt *S, BodyKind NewBodyKind) {
8937-
assert(getBodyKind() != BodyKind::Skipped &&
8938-
"cannot set a body if it was skipped");
8939-
89408937
llvm::Optional<Fingerprint> fp = llvm::None;
89418938
if (getBodyKind() == BodyKind::TypeChecked ||
89428939
getBodyKind() == BodyKind::Parsed) {
@@ -8952,6 +8949,65 @@ void AbstractFunctionDecl::setBody(BraceStmt *S, BodyKind NewBodyKind) {
89528949
}
89538950
}
89548951

8952+
bool AbstractFunctionDecl::isBodySkipped() const {
8953+
return evaluateOrDefault(getASTContext().evaluator,
8954+
IsFunctionBodySkippedRequest{this}, false);
8955+
}
8956+
8957+
/// Determines whether typechecking can be skipped for a function body. Bodies
8958+
/// are skipped as a performance optimization when an
8959+
/// `-experimental-skip-*-function-bodies` flag is specified and the body meets
8960+
/// the criteria for skipping. If a body is skipped during typechecking, it is
8961+
/// also skipped during SILGen. Some bodies cannot be skipped, even when they
8962+
/// otherwise meet the criteria, because typechecking them has essential
8963+
/// side-effects that are required for correctness of the AST.
8964+
bool IsFunctionBodySkippedRequest::evaluate(
8965+
Evaluator &evaluator, const AbstractFunctionDecl *afd) const {
8966+
auto &Ctx = afd->getASTContext();
8967+
auto skippingMode = Ctx.TypeCheckerOpts.SkipFunctionBodies;
8968+
if (skippingMode == FunctionBodySkipping::None)
8969+
return false;
8970+
8971+
// Functions that have been synthesized for clang modules will be serialized
8972+
// because they have shared linkage.
8973+
if (isa<ClangModuleUnit>(afd->getDeclContext()->getModuleScopeContext()))
8974+
return false;
8975+
8976+
if (auto *AD = dyn_cast<AccessorDecl>(afd)) {
8977+
// didSet accessors needs to be checked to determine whether to keep their
8978+
// parameters.
8979+
if (AD->getAccessorKind() == AccessorKind::DidSet)
8980+
return false;
8981+
8982+
// Synthesized accessors with forced static dispatch are emitted on-demand
8983+
// and are serialized. Since they are serialized we must be willing to
8984+
// typecheck them.
8985+
if (AD->hasForcedStaticDispatch())
8986+
return false;
8987+
}
8988+
8989+
// Actor initializers need to be checked to determine delegation status.
8990+
if (auto *ctor = dyn_cast<ConstructorDecl>(afd))
8991+
if (auto *nom = ctor->getParent()->getSelfNominalTypeDecl())
8992+
if (nom->isAnyActor())
8993+
return false;
8994+
8995+
// Skipping all bodies won't serialize anything, so we can skip everything
8996+
// else.
8997+
if (skippingMode == FunctionBodySkipping::All)
8998+
return true;
8999+
9000+
// If we want all types (for LLDB) then we can't skip functions with nested
9001+
// types. We could probably improve upon this and type-check only the nested
9002+
// types instead for better performances.
9003+
if (afd->hasNestedTypeDeclarations() &&
9004+
skippingMode == FunctionBodySkipping::NonInlinableWithoutTypes)
9005+
return false;
9006+
9007+
// Skip functions that don't need to be serialized.
9008+
return afd->getResilienceExpansion() != ResilienceExpansion::Minimal;
9009+
}
9010+
89559011
void AbstractFunctionDecl::setBodyToBeReparsed(SourceRange bodyRange) {
89569012
assert(bodyRange.isValid());
89579013
assert(getBodyKind() == BodyKind::Unparsed ||
@@ -8986,7 +9042,6 @@ SourceRange AbstractFunctionDecl::getBodySourceRange() const {
89869042

89879043
return SourceRange();
89889044

8989-
case BodyKind::Skipped:
89909045
case BodyKind::Unparsed:
89919046
return BodyRange;
89929047
}
@@ -9396,7 +9451,6 @@ bool AbstractFunctionDecl::hasInlinableBodyText() const {
93969451

93979452
case BodyKind::None:
93989453
case BodyKind::Synthesize:
9399-
case BodyKind::Skipped:
94009454
case BodyKind::SILSynthesize:
94019455
return false;
94029456
}
@@ -9847,8 +9901,7 @@ SourceRange FuncDecl::getSourceRange() const {
98479901
if (StartLoc.isInvalid())
98489902
return SourceRange();
98499903

9850-
if (getBodyKind() == BodyKind::Unparsed ||
9851-
getBodyKind() == BodyKind::Skipped)
9904+
if (getBodyKind() == BodyKind::Unparsed)
98529905
return { StartLoc, BodyRange.End };
98539906

98549907
SourceLoc RBraceLoc = getOriginalBodySourceRange().End;
@@ -10530,7 +10583,6 @@ ParseAbstractFunctionBodyRequest::getCachedResult() const {
1053010583
case BodyKind::Deserialized:
1053110584
case BodyKind::SILSynthesize:
1053210585
case BodyKind::None:
10533-
case BodyKind::Skipped:
1053410586
return BodyAndFingerprint{};
1053510587

1053610588
case BodyKind::TypeChecked:
@@ -10552,7 +10604,6 @@ void ParseAbstractFunctionBodyRequest::cacheResult(
1055210604
case BodyKind::Deserialized:
1055310605
case BodyKind::SILSynthesize:
1055410606
case BodyKind::None:
10555-
case BodyKind::Skipped:
1055610607
// The body is always empty, so don't cache anything.
1055710608
assert(!value.getFingerprint().has_value() && value.getBody() == nullptr);
1055810609
return;
@@ -10569,6 +10620,27 @@ void ParseAbstractFunctionBodyRequest::cacheResult(
1056910620
}
1057010621
}
1057110622

10623+
llvm::Optional<bool> IsFunctionBodySkippedRequest::getCachedResult() const {
10624+
using BodySkippedStatus = AbstractFunctionDecl::BodySkippedStatus;
10625+
auto afd = std::get<0>(getStorage());
10626+
switch (afd->getBodySkippedStatus()) {
10627+
case BodySkippedStatus::Unknown:
10628+
return llvm::None;
10629+
case BodySkippedStatus::Skipped:
10630+
return true;
10631+
case BodySkippedStatus::NotSkipped:
10632+
return false;
10633+
}
10634+
llvm_unreachable("bad BodySkippedStatus");
10635+
}
10636+
10637+
void IsFunctionBodySkippedRequest::cacheResult(bool isSkipped) const {
10638+
using BodySkippedStatus = AbstractFunctionDecl::BodySkippedStatus;
10639+
auto afd = std::get<0>(getStorage());
10640+
const_cast<AbstractFunctionDecl *>(afd)->setBodySkippedStatus(
10641+
isSkipped ? BodySkippedStatus::Skipped : BodySkippedStatus::NotSkipped);
10642+
}
10643+
1057210644
void swift::simple_display(llvm::raw_ostream &out, BodyAndFingerprint value) {
1057310645
out << "(";
1057410646
simple_display(out, value.getBody());

lib/AST/TypeCheckRequests.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1457,11 +1457,13 @@ llvm::Optional<BraceStmt *>
14571457
TypeCheckFunctionBodyRequest::getCachedResult() const {
14581458
using BodyKind = AbstractFunctionDecl::BodyKind;
14591459
auto *afd = std::get<0>(getStorage());
1460+
if (afd->isBodySkipped())
1461+
return nullptr;
1462+
14601463
switch (afd->getBodyKind()) {
14611464
case BodyKind::Deserialized:
14621465
case BodyKind::SILSynthesize:
14631466
case BodyKind::None:
1464-
case BodyKind::Skipped:
14651467
// These cases don't have any body available.
14661468
return nullptr;
14671469

lib/Parse/ParseRequests.cpp

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,6 @@ ParseAbstractFunctionBodyRequest::evaluate(Evaluator &evaluator,
9191
case BodyKind::Deserialized:
9292
case BodyKind::SILSynthesize:
9393
case BodyKind::None:
94-
case BodyKind::Skipped:
9594
return {};
9695

9796
case BodyKind::TypeChecked:

0 commit comments

Comments
 (0)