Sema: Expand TypeRefinementContexts lazily for unparsed function bodies.

When building the TypeRefinementContext subtree for a function declaration,
postpone creation of the subtree if the function body is unparsed. This allows
the compiler to completely avoid parsing function bodies that have been skipped
(e.g. with -experimental-skip-non-inlinable-function-bodies) while still
ensuring that the TRCs for functions are built lazily later if needed. When
lazily generating SIL for a function with -experimental-lazy-typecheck, the
TRCs must be built out while typechecking the function in order to emit correct
diagnostics and SIL for `if #available` queries.

Resolves rdar://117448323
This commit is contained in:
Allan Shortlidge
2023-11-08 20:03:55 -08:00
parent c8e63ff113
commit 2aed784ede
5 changed files with 87 additions and 54 deletions

View File

@@ -512,35 +512,55 @@ private:
return Action::Continue();
}
/// Constructs a placeholder TRC node that should be expanded later if the AST
/// associated with the given declaration is not ready to be traversed yet.
/// Returns true if a node was created.
bool buildLazyContextForDecl(Decl *D) {
if (!isa<PatternBindingDecl>(D))
return false;
bool shouldBuildLazyContextForDecl(Decl *D) {
// Skip functions that have unparsed bodies on an initial descent to avoid
// eagerly parsing bodies unnecessarily.
if (auto *afd = dyn_cast<AbstractFunctionDecl>(D)) {
if (afd->hasBody() && !afd->isBodySkipped() &&
!afd->getBody(/*canSynthesize=*/false))
return true;
}
// Pattern binding declarations may have attached property wrappers that
// get expanded from macros attached to the parent declaration. We must
// not eagerly expand the attached property wrappers to avoid request
// cycles.
if (auto *pattern = dyn_cast<PatternBindingDecl>(D)) {
if (auto firstVar = pattern->getAnchoringVarDecl(0)) {
// FIXME: We could narrow this further by detecting whether there are
// any macro expansions required to visit the CustomAttrs of the var.
if (firstVar->hasInitialValue() && !firstVar->isInitExposedToClients())
return true;
}
}
return false;
}
/// For declarations that were previously skipped prepare the AST before
/// building out TRCs.
void prepareDeclForLazyExpansion(Decl *D) {
if (auto AFD = dyn_cast<AbstractFunctionDecl>(D)) {
(void)AFD->getBody(/*canSynthesize*/ true);
}
}
/// Constructs a placeholder TRC node that should be expanded later. This is
/// useful for postponing unnecessary work (and request triggers) when
/// initally building out the TRC subtree under a declaration. Lazy nodes
/// constructed here will be expanded by
/// ExpandChildTypeRefinementContextsRequest. Returns true if a node was
/// created.
bool buildLazyContextForDecl(Decl *D) {
// Check whether the current TRC is already a lazy placeholder. If it is,
// we should try to expand it rather than creating a new placeholder.
auto currentTRC = getCurrentTRC();
if (currentTRC->getNeedsExpansion() && currentTRC->getDeclOrNull() == D)
return false;
// Pattern binding declarations may have attached property wrappers that
// get expanded from macros attached to the parent declaration. We must
// not eagerly expand the attached property wrappers to avoid a request
// cycle.
if (auto *pattern = dyn_cast<PatternBindingDecl>(D)) {
if (auto firstVar = pattern->getAnchoringVarDecl(0)) {
// If there's no initial value, or the init is exposed to clients, then
// we don't need to create any implicit TRCs for the init bodies.
if (!firstVar->hasInitialValue() || firstVar->isInitExposedToClients())
if (!shouldBuildLazyContextForDecl(D))
return false;
// FIXME: We could narrow this further by detecting whether there are
// any macro expansions required to visit the CustomAttrs of the var.
}
}
// If we've made it this far then we've identified a declaration that
// requires lazy expansion later.
auto lazyTRC = TypeRefinementContext::createForDeclImplicit(
@@ -1223,6 +1243,7 @@ private:
void TypeChecker::buildTypeRefinementContextHierarchy(SourceFile &SF) {
TypeRefinementContext *RootTRC = SF.getTypeRefinementContext();
ASTContext &Context = SF.getASTContext();
assert(!Context.LangOpts.DisableAvailabilityChecking);
if (!RootTRC) {
// The root type refinement context reflects the fact that all parts of
@@ -1246,28 +1267,11 @@ void TypeChecker::buildTypeRefinementContextHierarchy(SourceFile &SF) {
}
}
void TypeChecker::buildTypeRefinementContextHierarchyDelayed(SourceFile &SF, AbstractFunctionDecl *AFD) {
// If there's no TRC for the file, we likely don't want this one either.
// RootTRC is not set when availability checking is disabled.
TypeRefinementContext *RootTRC = SF.getTypeRefinementContext();
if(!RootTRC)
return;
if (AFD->getBodyKind() != AbstractFunctionDecl::BodyKind::Unparsed)
return;
// Parse the function body.
AFD->getBody(/*canSynthesize=*/true);
// Build the refinement context for the function body.
ASTContext &Context = SF.getASTContext();
auto LocalTRC = RootTRC->findMostRefinedSubContext(AFD->getLoc(), Context);
TypeRefinementContextBuilder Builder(LocalTRC, Context);
Builder.build(AFD);
}
TypeRefinementContext *
TypeChecker::getOrBuildTypeRefinementContext(SourceFile *SF) {
if (SF->getASTContext().LangOpts.DisableAvailabilityChecking)
return nullptr;
TypeRefinementContext *TRC = SF->getTypeRefinementContext();
if (!TRC) {
buildTypeRefinementContextHierarchy(*SF);
@@ -1284,6 +1288,7 @@ ExpandChildTypeRefinementContextsRequest::evaluate(
if (auto decl = parentTRC->getDeclOrNull()) {
ASTContext &ctx = decl->getASTContext();
TypeRefinementContextBuilder builder(parentTRC, ctx);
builder.prepareDeclForLazyExpansion(decl);
builder.build(decl);
}
return parentTRC->Children;
@@ -1338,6 +1343,7 @@ TypeChecker::overApproximateAvailabilityAtLocation(SourceLoc loc,
if (SF && loc.isValid()) {
TypeRefinementContext *rootTRC = getOrBuildTypeRefinementContext(SF);
if (rootTRC) {
TypeRefinementContext *TRC =
rootTRC->findMostRefinedSubContext(loc, Context);
OverApproximateContext.constrainWith(TRC->getAvailabilityInfo());
@@ -1345,6 +1351,7 @@ TypeChecker::overApproximateAvailabilityAtLocation(SourceLoc loc,
*MostRefined = TRC;
}
}
}
return OverApproximateContext;
}

View File

@@ -2729,10 +2729,6 @@ TypeCheckFunctionBodyRequest::evaluate(Evaluator &eval,
if (tyOpts.DebugTimeFunctionBodies || tyOpts.WarnLongFunctionBodies)
timer.emplace(AFD);
auto SF = AFD->getParentSourceFile();
if (SF)
TypeChecker::buildTypeRefinementContextHierarchyDelayed(*SF, AFD);
BraceStmt *body = AFD->getBody();
assert(body && "Expected body to type-check");

View File

@@ -1056,10 +1056,6 @@ AvailabilityContext overApproximateAvailabilityAtLocation(
/// Walk the AST to build the hierarchy of TypeRefinementContexts
void buildTypeRefinementContextHierarchy(SourceFile &SF);
/// Walk the AST to complete the hierarchy of TypeRefinementContexts for
/// the delayed function body of \p AFD.
void buildTypeRefinementContextHierarchyDelayed(SourceFile &SF, AbstractFunctionDecl *AFD);
/// Build the hierarchy of TypeRefinementContexts for the entire
/// source file, if it has not already been built. Returns the root
/// TypeRefinementContext for the source file.

View File

@@ -4,6 +4,7 @@
// -enable-experimental-feature requires an asserts build
// REQUIRES: asserts
// REQUIRES: rdar118189728
// Tests that parsing of function bodies is deferred
// to runtime when the interpreter is invoked using

View File

@@ -0,0 +1,33 @@
// RUN: %target-swift-frontend -emit-silgen %s -parse-as-library -module-name Test -experimental-lazy-typecheck -experimental-skip-non-inlinable-function-bodies | %FileCheck %s
// Note: This test has been carefully constructed to create the preconditions of
// a lazy typechecking bug. First, foo() is parsed and typechecked lazily in
// order to generate SIL for it. Type checking the body of foo() causes the
// TypeRefinementContext tree to be built for the file, but bar() has not been
// parsed yet so it gets skipped during construction of the tree. Therefore
// when generating the SIL for bar() its TRC must be created on-demand in order
// to emit the correct SIL for the if #available condition.
// REQUIRES: OS=macosx
// CHECK: sil{{.*}} @$s4Test3fooyS2iF : $@convention(thin) (Int) -> Int {
// CHECK: } // end sil function '$s4Test3fooyS2iF'
@inlinable
public func foo(_ x: Int) -> Int {
_ = x
}
// CHECK: sil{{.*}} @$s4Test3barSiyF : $@convention(thin) () -> Int {
// CHECK: {{.*}} = integer_literal $Builtin.Word, 11
// CHECK: {{.*}} = integer_literal $Builtin.Word, 0
// CHECK: {{.*}} = integer_literal $Builtin.Word, 0
// CHECK: {{.*}} = function_ref @$ss26_stdlib_isOSVersionAtLeastyBi1_Bw_BwBwtF
// CHECK: } // end sil function '$s4Test3barSiyF'
@inlinable
public func bar() -> Int {
if #available(macOS 11.0, *) {
return 1
} else {
return 2
}
}