Skip to content

AST/SILGen: Requestify function body skipping #68760

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 1 commit into from
Sep 29, 2023
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
54 changes: 26 additions & 28 deletions include/swift/AST/Decl.h
Original file line number Diff line number Diff line change
Expand Up @@ -450,10 +450,13 @@ class alignas(1 << DeclAlignInBits) Decl : public ASTAllocated<Decl> {
SWIFT_INLINE_BITFIELD(SubscriptDecl, VarDecl, 2,
StaticSpelling : 2
);
SWIFT_INLINE_BITFIELD(AbstractFunctionDecl, ValueDecl, 3+2+8+1+1+1+1+1+1+1,
SWIFT_INLINE_BITFIELD(AbstractFunctionDecl, ValueDecl, 3+2+2+8+1+1+1+1+1+1+1,
/// \see AbstractFunctionDecl::BodyKind
BodyKind : 3,

/// \see AbstractFunctionDecl::BodySkippedStatus
BodySkippedStatus : 2,

/// \see AbstractFunctionDecl::SILSynthesizeKind
SILSynthesizeKind : 2,

Expand Down Expand Up @@ -6857,9 +6860,6 @@ class AbstractFunctionDecl : public GenericContext, public ValueDecl {
/// Function body is parsed and available as an AST subtree.
Parsed,

/// Function body is not available, although it was written in the source.
Skipped,

/// Function body will be synthesized on demand.
Synthesize,

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

enum class BodySkippedStatus {
Unknown,
Skipped,
NotSkipped,

// This enum needs to fit in a 2-bit bitfield.
};

BodyKind getBodyKind() const {
return BodyKind(Bits.AbstractFunctionDecl.BodyKind);
}
Expand Down Expand Up @@ -6926,13 +6934,13 @@ class AbstractFunctionDecl : public GenericContext, public ValueDecl {

/// The location of the function body when the body is delayed or skipped.
///
/// This enum member is active if getBodyKind() is BodyKind::Unparsed or
/// BodyKind::Skipped.
/// This enum member is active if getBodyKind() is BodyKind::Unparsed.
SourceRange BodyRange;
};

friend class ParseAbstractFunctionBodyRequest;
friend class TypeCheckFunctionBodyRequest;
friend class IsFunctionBodySkippedRequest;

CaptureInfo Captures;

Expand Down Expand Up @@ -6970,6 +6978,14 @@ class AbstractFunctionDecl : public GenericContext, public ValueDecl {
Bits.AbstractFunctionDecl.BodyKind = unsigned(K);
}

BodySkippedStatus getBodySkippedStatus() const {
return BodySkippedStatus(Bits.AbstractFunctionDecl.BodySkippedStatus);
}

void setBodySkippedStatus(BodySkippedStatus status) {
Bits.AbstractFunctionDecl.BodySkippedStatus = unsigned(status);
}

void setSILSynthesizeKind(SILSynthesizeKind K) {
Bits.AbstractFunctionDecl.SILSynthesizeKind = unsigned(K);
}
Expand Down Expand Up @@ -7088,10 +7104,7 @@ 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 &&
getBodyKind() != BodyKind::Skipped;
}
bool hasBody() const { return getBodyKind() != BodyKind::None; }

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

/// Note that the body was skipped for this function. Function body
/// cannot be attached after this call.
void setBodySkipped(SourceRange bodyRange) {
// FIXME: Remove 'Parsed' from this list once we can always delay
// parsing bodies. The -experimental-skip-*-function-bodies options
// do currently skip parsing, unless disabled through other means in
// SourceFile::hasDelayedBodyParsing.
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);
Expand Down Expand Up @@ -7205,9 +7203,9 @@ class AbstractFunctionDecl : public GenericContext, public ValueDecl {
return getBodyKind() == BodyKind::SILSynthesize;
}

bool isBodySkipped() const {
return getBodyKind() == BodyKind::Skipped;
}
/// Indicates whether the body of this function is skipped during
/// typechecking.
bool isBodySkipped() const;

bool isMemberwiseInitializer() const {
return getBodyKind() == BodyKind::SILSynthesize
Expand Down
19 changes: 19 additions & 0 deletions include/swift/AST/TypeCheckRequests.h
Original file line number Diff line number Diff line change
Expand Up @@ -4423,6 +4423,25 @@ class SerializeAttrGenericSignatureRequest
void cacheResult(GenericSignature signature) const;
};

class IsFunctionBodySkippedRequest
: public SimpleRequest<IsFunctionBodySkippedRequest,
bool(const AbstractFunctionDecl *),
RequestFlags::SeparatelyCached> {
public:
using SimpleRequest::SimpleRequest;

private:
friend SimpleRequest;

bool evaluate(Evaluator &evaluator, const AbstractFunctionDecl *) const;

public:
// Separate caching.
bool isCached() const { return true; }
llvm::Optional<bool> getCachedResult() const;
void cacheResult(bool isSkipped) const;
};

#define SWIFT_TYPEID_ZONE TypeChecker
#define SWIFT_TYPEID_HEADER "swift/AST/TypeCheckerTypeIDZone.def"
#include "swift/Basic/DefineTypeIDZone.h"
Expand Down
3 changes: 3 additions & 0 deletions include/swift/AST/TypeCheckerTypeIDZone.def
Original file line number Diff line number Diff line change
Expand Up @@ -503,3 +503,6 @@ SWIFT_REQUEST(TypeChecker, ExpandChildTypeRefinementContextsRequest,
SWIFT_REQUEST(TypeChecker, SerializeAttrGenericSignatureRequest,
GenericSignature(Decl *, SpecializeAttr *),
SeparatelyCached, NoLocationInfo)
SWIFT_REQUEST(TypeChecker, IsFunctionBodySkippedRequest,
bool(const AbstractFunctionDecl *),
SeparatelyCached, NoLocationInfo)
1 change: 0 additions & 1 deletion lib/AST/ASTVerifier.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -496,7 +496,6 @@ class Verifier : public ASTWalker {
switch (afd->getBodyKind()) {
case AbstractFunctionDecl::BodyKind::None:
case AbstractFunctionDecl::BodyKind::TypeChecked:
case AbstractFunctionDecl::BodyKind::Skipped:
case AbstractFunctionDecl::BodyKind::SILSynthesize:
case AbstractFunctionDecl::BodyKind::Deserialized:
return true;
Expand Down
97 changes: 88 additions & 9 deletions lib/AST/Decl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8934,9 +8934,6 @@ BraceStmt *AbstractFunctionDecl::getTypecheckedBody() const {
}

void AbstractFunctionDecl::setBody(BraceStmt *S, BodyKind NewBodyKind) {
assert(getBodyKind() != BodyKind::Skipped &&
"cannot set a body if it was skipped");

llvm::Optional<Fingerprint> fp = llvm::None;
if (getBodyKind() == BodyKind::TypeChecked ||
getBodyKind() == BodyKind::Parsed) {
Expand All @@ -8952,6 +8949,72 @@ void AbstractFunctionDecl::setBody(BraceStmt *S, BodyKind NewBodyKind) {
}
}

bool AbstractFunctionDecl::isBodySkipped() const {
return evaluateOrDefault(getASTContext().evaluator,
IsFunctionBodySkippedRequest{this}, false);
}

/// Determines whether typechecking can be skipped for a function body. Bodies
/// are skipped as a performance optimization when an
/// `-experimental-skip-*-function-bodies` flag is specified and the body meets
/// the criteria for skipping. If a body is skipped during typechecking, it is
/// also skipped during SILGen. Some bodies cannot be skipped, even when they
/// otherwise meet the criteria, because typechecking them has essential
/// side-effects that are required for correctness of the AST.
bool IsFunctionBodySkippedRequest::evaluate(
Evaluator &evaluator, const AbstractFunctionDecl *afd) const {
auto &Ctx = afd->getASTContext();
auto skippingMode = Ctx.TypeCheckerOpts.SkipFunctionBodies;
if (skippingMode == FunctionBodySkipping::None)
return false;

// Functions that have been synthesized for clang modules will be serialized
// because they have shared linkage.
if (isa<ClangModuleUnit>(afd->getDeclContext()->getModuleScopeContext()))
return false;

if (auto *accessor = dyn_cast<AccessorDecl>(afd)) {
// didSet accessors needs to be checked to determine whether to keep their
// parameters.
if (accessor->getAccessorKind() == AccessorKind::DidSet)
return false;

// Synthesized accessors with forced static dispatch are emitted on-demand
// and are serialized. Since they are serialized we must be willing to
// typecheck them.
if (accessor->hasForcedStaticDispatch())
return false;

if (auto *varDecl = dyn_cast<VarDecl>(accessor->getStorage())) {
// FIXME: If we don't typecheck the synthesized accessors of lazy storage
// properties then SILGen crashes when emitting the initializer.
if (varDecl->getAttrs().hasAttribute<LazyAttr>() && accessor->isSynthesized())
return false;
}
}

// Actor initializers need to be checked to determine delegation status.
if (auto *ctor = dyn_cast<ConstructorDecl>(afd))
if (auto *nom = ctor->getParent()->getSelfNominalTypeDecl())
if (nom->isAnyActor())
return false;

// Skipping all bodies won't serialize anything, so we can skip everything
// else.
if (skippingMode == FunctionBodySkipping::All)
return true;

// If we want all types (for LLDB) then we can't skip functions with nested
// types. We could probably improve upon this and type-check only the nested
// types instead for better performances.
if (afd->hasNestedTypeDeclarations() &&
skippingMode == FunctionBodySkipping::NonInlinableWithoutTypes)
return false;

// Skip functions that don't need to be serialized.
return afd->getResilienceExpansion() != ResilienceExpansion::Minimal;
}

void AbstractFunctionDecl::setBodyToBeReparsed(SourceRange bodyRange) {
assert(bodyRange.isValid());
assert(getBodyKind() == BodyKind::Unparsed ||
Expand Down Expand Up @@ -8986,7 +9049,6 @@ SourceRange AbstractFunctionDecl::getBodySourceRange() const {

return SourceRange();

case BodyKind::Skipped:
case BodyKind::Unparsed:
return BodyRange;
}
Expand Down Expand Up @@ -9396,7 +9458,6 @@ bool AbstractFunctionDecl::hasInlinableBodyText() const {

case BodyKind::None:
case BodyKind::Synthesize:
case BodyKind::Skipped:
case BodyKind::SILSynthesize:
return false;
}
Expand Down Expand Up @@ -9847,8 +9908,7 @@ SourceRange FuncDecl::getSourceRange() const {
if (StartLoc.isInvalid())
return SourceRange();

if (getBodyKind() == BodyKind::Unparsed ||
getBodyKind() == BodyKind::Skipped)
if (getBodyKind() == BodyKind::Unparsed)
return { StartLoc, BodyRange.End };

SourceLoc RBraceLoc = getOriginalBodySourceRange().End;
Expand Down Expand Up @@ -10530,7 +10590,6 @@ ParseAbstractFunctionBodyRequest::getCachedResult() const {
case BodyKind::Deserialized:
case BodyKind::SILSynthesize:
case BodyKind::None:
case BodyKind::Skipped:
return BodyAndFingerprint{};

case BodyKind::TypeChecked:
Expand All @@ -10552,7 +10611,6 @@ void ParseAbstractFunctionBodyRequest::cacheResult(
case BodyKind::Deserialized:
case BodyKind::SILSynthesize:
case BodyKind::None:
case BodyKind::Skipped:
// The body is always empty, so don't cache anything.
assert(!value.getFingerprint().has_value() && value.getBody() == nullptr);
return;
Expand All @@ -10569,6 +10627,27 @@ void ParseAbstractFunctionBodyRequest::cacheResult(
}
}

llvm::Optional<bool> IsFunctionBodySkippedRequest::getCachedResult() const {
using BodySkippedStatus = AbstractFunctionDecl::BodySkippedStatus;
auto afd = std::get<0>(getStorage());
switch (afd->getBodySkippedStatus()) {
case BodySkippedStatus::Unknown:
return llvm::None;
case BodySkippedStatus::Skipped:
return true;
case BodySkippedStatus::NotSkipped:
return false;
}
llvm_unreachable("bad BodySkippedStatus");
}

void IsFunctionBodySkippedRequest::cacheResult(bool isSkipped) const {
using BodySkippedStatus = AbstractFunctionDecl::BodySkippedStatus;
auto afd = std::get<0>(getStorage());
const_cast<AbstractFunctionDecl *>(afd)->setBodySkippedStatus(
isSkipped ? BodySkippedStatus::Skipped : BodySkippedStatus::NotSkipped);
}

void swift::simple_display(llvm::raw_ostream &out, BodyAndFingerprint value) {
out << "(";
simple_display(out, value.getBody());
Expand Down
4 changes: 3 additions & 1 deletion lib/AST/TypeCheckRequests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1457,11 +1457,13 @@ llvm::Optional<BraceStmt *>
TypeCheckFunctionBodyRequest::getCachedResult() const {
using BodyKind = AbstractFunctionDecl::BodyKind;
auto *afd = std::get<0>(getStorage());
if (afd->isBodySkipped())
return nullptr;

switch (afd->getBodyKind()) {
case BodyKind::Deserialized:
case BodyKind::SILSynthesize:
case BodyKind::None:
case BodyKind::Skipped:
// These cases don't have any body available.
return nullptr;

Expand Down
1 change: 0 additions & 1 deletion lib/Parse/ParseRequests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,6 @@ ParseAbstractFunctionBodyRequest::evaluate(Evaluator &evaluator,
case BodyKind::Deserialized:
case BodyKind::SILSynthesize:
case BodyKind::None:
case BodyKind::Skipped:
return {};

case BodyKind::TypeChecked:
Expand Down
Loading