mirror of
https://github.com/apple/swift.git
synced 2025-12-21 12:14:44 +01:00
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:
@@ -512,34 +512,54 @@ 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())
|
||||
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 (!shouldBuildLazyContextForDecl(D))
|
||||
return false;
|
||||
|
||||
// If we've made it this far then we've identified a declaration that
|
||||
// requires lazy expansion later.
|
||||
@@ -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,11 +1343,13 @@ TypeChecker::overApproximateAvailabilityAtLocation(SourceLoc loc,
|
||||
|
||||
if (SF && loc.isValid()) {
|
||||
TypeRefinementContext *rootTRC = getOrBuildTypeRefinementContext(SF);
|
||||
TypeRefinementContext *TRC =
|
||||
rootTRC->findMostRefinedSubContext(loc, Context);
|
||||
OverApproximateContext.constrainWith(TRC->getAvailabilityInfo());
|
||||
if (MostRefined) {
|
||||
*MostRefined = TRC;
|
||||
if (rootTRC) {
|
||||
TypeRefinementContext *TRC =
|
||||
rootTRC->findMostRefinedSubContext(loc, Context);
|
||||
OverApproximateContext.constrainWith(TRC->getAvailabilityInfo());
|
||||
if (MostRefined) {
|
||||
*MostRefined = TRC;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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");
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
33
test/SILGen/lazy_typecheck_availability.swift
Normal file
33
test/SILGen/lazy_typecheck_availability.swift
Normal 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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user