Skip to content

Commit 02185c3

Browse files
committed
Sema: When validating members for layout, skip certain members
For all types, we can safely skip nested nominal types and typealiases. For a struct, we only have to look at VarDecls; methods never affect layout. Similarly for an enum, only EnumElementDecls matter. For a class, we still have to look at all methods and properties. Ideally, in non-optimized builds we would invoke virtual methods by calling thunks, and only emit the thunks from the translation unit containing the class. Then the layout of a class will only be necessary if you subclass the class. This should improve compiler scalability in multiple-frontend mode.
1 parent 208dec7 commit 02185c3

File tree

3 files changed

+120
-44
lines changed

3 files changed

+120
-44
lines changed

Diff for: lib/Sema/TypeChecker.cpp

+82-44
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,87 @@ void TypeChecker::bindExtension(ExtensionDecl *ext) {
407407
::bindExtensionDecl(ext, *this);
408408
}
409409

410+
static bool shouldValidateDeclForLayout(NominalTypeDecl *nominal, ValueDecl *VD) {
411+
// For enums, we only need to validate enum elements to know
412+
// the layout.
413+
if (isa<EnumDecl>(nominal) &&
414+
isa<EnumElementDecl>(VD))
415+
return true;
416+
417+
// For structs, we only need to validate stored properties to
418+
// know the layout.
419+
if (isa<StructDecl>(nominal) &&
420+
(isa<VarDecl>(VD) &&
421+
!cast<VarDecl>(VD)->isStatic() &&
422+
(cast<VarDecl>(VD)->hasStorage() ||
423+
VD->getAttrs().hasAttribute<LazyAttr>())))
424+
return true;
425+
426+
// For classes, we need to validate properties and functions,
427+
// but skipping nested types is OK.
428+
if (isa<ClassDecl>(nominal) &&
429+
!isa<TypeDecl>(VD))
430+
return true;
431+
432+
// For protocols, skip nested typealiases and nominal types.
433+
if (isa<ProtocolDecl>(nominal) &&
434+
!isa<GenericTypeDecl>(VD))
435+
return true;
436+
437+
return false;
438+
}
439+
440+
static void validateDeclForLayout(TypeChecker &TC, NominalTypeDecl *nominal) {
441+
Optional<bool> lazyVarsAlreadyHaveImplementation;
442+
443+
for (auto *D : nominal->getMembers()) {
444+
auto VD = dyn_cast<ValueDecl>(D);
445+
if (!VD)
446+
continue;
447+
448+
if (!shouldValidateDeclForLayout(nominal, VD))
449+
continue;
450+
451+
TC.validateDecl(VD);
452+
453+
// The only thing left to do is synthesize storage for lazy variables.
454+
// We only have to do that if it's a type from another file, though.
455+
// In NDEBUG builds, bail out as soon as we can.
456+
#ifdef NDEBUG
457+
if (lazyVarsAlreadyHaveImplementation.hasValue() &&
458+
lazyVarsAlreadyHaveImplementation.getValue())
459+
continue;
460+
#endif
461+
auto *prop = dyn_cast<VarDecl>(D);
462+
if (!prop)
463+
continue;
464+
465+
if (prop->getAttrs().hasAttribute<LazyAttr>() && !prop->isStatic()
466+
&& prop->getGetter()) {
467+
bool hasImplementation = prop->getGetter()->hasBody();
468+
469+
if (lazyVarsAlreadyHaveImplementation.hasValue()) {
470+
assert(lazyVarsAlreadyHaveImplementation.getValue() ==
471+
hasImplementation &&
472+
"only some lazy vars already have implementations");
473+
} else {
474+
lazyVarsAlreadyHaveImplementation = hasImplementation;
475+
}
476+
477+
if (!hasImplementation)
478+
TC.completeLazyVarImplementation(prop);
479+
}
480+
}
481+
482+
// FIXME: We need to add implicit initializers and dtors when a decl is
483+
// touched, because it affects vtable layout. If you're not defining the
484+
// class, you shouldn't have to know what the vtable layout is.
485+
if (auto *CD = dyn_cast<ClassDecl>(nominal)) {
486+
TC.addImplicitConstructors(CD);
487+
TC.addImplicitDestructor(CD);
488+
}
489+
}
490+
410491
static void typeCheckFunctionsAndExternalDecls(TypeChecker &TC) {
411492
unsigned currentFunctionIdx = 0;
412493
unsigned currentExternalDef = TC.Context.LastCheckedExternalDefinition;
@@ -462,50 +543,7 @@ static void typeCheckFunctionsAndExternalDecls(TypeChecker &TC) {
462543
if (nominal->isInvalid() || TC.Context.hadError())
463544
continue;
464545

465-
Optional<bool> lazyVarsAlreadyHaveImplementation;
466-
467-
for (auto *D : nominal->getMembers()) {
468-
auto VD = dyn_cast<ValueDecl>(D);
469-
if (!VD)
470-
continue;
471-
TC.validateDecl(VD);
472-
473-
// The only thing left to do is synthesize storage for lazy variables.
474-
// We only have to do that if it's a type from another file, though.
475-
// In NDEBUG builds, bail out as soon as we can.
476-
#ifdef NDEBUG
477-
if (lazyVarsAlreadyHaveImplementation.hasValue() &&
478-
lazyVarsAlreadyHaveImplementation.getValue())
479-
continue;
480-
#endif
481-
auto *prop = dyn_cast<VarDecl>(D);
482-
if (!prop)
483-
continue;
484-
485-
if (prop->getAttrs().hasAttribute<LazyAttr>() && !prop->isStatic()
486-
&& prop->getGetter()) {
487-
bool hasImplementation = prop->getGetter()->hasBody();
488-
489-
if (lazyVarsAlreadyHaveImplementation.hasValue()) {
490-
assert(lazyVarsAlreadyHaveImplementation.getValue() ==
491-
hasImplementation &&
492-
"only some lazy vars already have implementations");
493-
} else {
494-
lazyVarsAlreadyHaveImplementation = hasImplementation;
495-
}
496-
497-
if (!hasImplementation)
498-
TC.completeLazyVarImplementation(prop);
499-
}
500-
}
501-
502-
// FIXME: We need to add implicit initializers and dtors when a decl is
503-
// touched, because it affects vtable layout. If you're not defining the
504-
// class, you shouldn't have to know what the vtable layout is.
505-
if (auto *CD = dyn_cast<ClassDecl>(nominal)) {
506-
TC.addImplicitConstructors(CD);
507-
TC.addImplicitDestructor(CD);
508-
}
546+
validateDeclForLayout(TC, nominal);
509547
}
510548

511549
// Complete any conformances that we used.

Diff for: validation-test/compiler_scale/enum_members.gyb

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// RUN: %scale-test --sum-multi --typecheck --begin 5 --end 16 --step 5 --select validateDecl %s
2+
// REQUIRES: OS=macosx
3+
4+
enum Enum${N} {
5+
case OK
6+
case Error
7+
8+
% if int(N) > 1:
9+
func method(_: Enum${int(N)-1}) {}
10+
static var prop = Enum${int(N)-1}.OK
11+
subscript(n: Int) -> Enum${int(N)-1} { return Enum${int(N)-1}.OK }
12+
% else:
13+
func method() {}
14+
static var prop = 0
15+
subscript(n: Int) -> Int { return 0 }
16+
% end
17+
18+
struct Nested {}
19+
}
20+

Diff for: validation-test/compiler_scale/struct_members.gyb

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// RUN: %scale-test --sum-multi --typecheck --begin 5 --end 16 --step 5 --select validateDecl %s
2+
// REQUIRES: OS=macosx
3+
4+
struct Struct${N} {
5+
% if int(N) > 1:
6+
func method(_: Struct${int(N)-1}) {}
7+
var prop: Struct${int(N)-1} { return Struct${int(N)-1}() }
8+
static var prop = Struct${int(N)-1}()
9+
subscript(n: Int) -> Struct${int(N)-1} { return Struct${int(N)-1}() }
10+
% else:
11+
func method() {}
12+
var prop: Int { return 0 }
13+
static var prop = 0
14+
subscript(n: Int) -> Int { return 0 }
15+
% end
16+
17+
struct Nested {}
18+
}

0 commit comments

Comments
 (0)