Skip to content

Commit 56484c9

Browse files
Reduce types loaded on startup significantly (#120459)
Reduce the number of type loads triggered by complex generic constraints. Experiments show about a 20% reduction in startup time on a proxy of the AWS lambda scenario. The fixes are: 1. When a method/type has constraints, do not load the constraint types directly if they are generic interfaces. Instead, do accessibility checking and the constraint validation on the signature of the constraint instead. 2. When a type is loaded, enhance the special marker type optimization in the interface map to allow Interfaces which require implementation of other interfaces to use the special marker type. Notably, use the special marker type so that if a generic interface's first type parameter is used as all of the generic type parameters of a required interface we use the special marker type to indicate that the type is loaded. - This also forces us to have a somewhat more complicated implementation of casting, since the previous model of special marker types could not experience an equivalent interface match 3. When resolving a default interface method, attempt to avoid loading all the interface types associated with a type. Do so by changing the iteration of interfaces to check to see if the approximate interface could possibly have an implementation 4. When resolving a static virtual method, attempt to avoid loading all the types of the MethodImpl records. Do so by doing a Method name check before loading the type. In addition, fix a test so that it still throws when loading an invalid type. This required adding a path which forced the interface type to be loaded. As part of testing this work, the lack of generic variable type safety checks was noticed in crossgen2, and the following changes were made - Add support for doing circularity checks on type and method generic parameters to the TypeValidationChecker in crossgen2 - Add support for doing variance safety checks to the type loader in crossgen2 And in the runtime - Remove the circularity of type variables checks happening in generic method load, as it is redundant - Put all of the variance safety checks in the runtime under the SkipTypeValidation flag as crossgen2 can now do it reliably Related to the work in #120407
1 parent ce717ac commit 56484c9

File tree

21 files changed

+923
-255
lines changed

21 files changed

+923
-255
lines changed

src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/TypeValidationChecker.cs

Lines changed: 186 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System;
55
using System.Collections.Concurrent;
66
using System.Collections.Generic;
7+
using System.Diagnostics;
78
using System.Linq;
89
using System.Reflection;
910
using System.Text;
@@ -133,6 +134,18 @@ Task<bool> ValidateTypeWorkerHelper(TypeDesc typeToCheckForSkipValidation)
133134
AddTypeValidationError(type, $"Interface type {interfaceType} failed validation");
134135
return false;
135136
}
137+
138+
if (!interfaceType.IsInterface)
139+
{
140+
AddTypeValidationError(type, $"Runtime interface {interfaceType} is not an interface");
141+
return false;
142+
}
143+
144+
if (interfaceType.HasInstantiation)
145+
{
146+
// Interfaces behave covariantly
147+
ValidateVarianceInType(type.Instantiation, interfaceType, Internal.TypeSystem.GenericVariance.Covariant);
148+
}
136149
}
137150

138151
// Validate that each interface type explicitly implemented on this type is accessible to this type -- UNIMPLEMENTED
@@ -293,14 +306,76 @@ Task<bool> ValidateTypeWorkerHelper(TypeDesc typeToCheckForSkipValidation)
293306
AddTypeValidationError(type, $"'{method}' is an runtime-impl generic method");
294307
return false;
295308
}
296-
// Validate that generic variance is properly respected in method signatures -- UNIMPLEMENTED
297-
// Validate that there are no cyclical method constraints -- UNIMPLEMENTED
309+
310+
311+
// Validate that generic variance is properly respected in method signatures
312+
if (type.IsInterface && method.IsVirtual && !method.Signature.IsStatic && type.HasInstantiation)
313+
{
314+
if (!ValidateVarianceInSignature(type.Instantiation, method.Signature, Internal.TypeSystem.GenericVariance.Covariant, Internal.TypeSystem.GenericVariance.Contravariant))
315+
{
316+
AddTypeValidationError(type, $"'{method}' has invalid variance in signature");
317+
return false;
318+
}
319+
}
320+
321+
foreach (var genericParameterType in method.Instantiation)
322+
{
323+
foreach (var constraint in ((GenericParameterDesc)genericParameterType).TypeConstraints)
324+
{
325+
if (!ValidateVarianceInType(type.Instantiation, constraint, Internal.TypeSystem.GenericVariance.Contravariant))
326+
{
327+
AddTypeValidationError(type, $"'{method}' has invalid variance in generic parameter constraint {constraint} on type {genericParameterType}");
328+
return false;
329+
}
330+
}
331+
}
332+
333+
if (!BoundedCheckForInstantiation(method.Instantiation))
334+
{
335+
AddTypeValidationError(type, $"'{method}' has cyclical generic parameter constraints");
336+
return false;
337+
}
298338
// Validate that constraints are all acccessible to the method using them -- UNIMPLEMENTED
299339
}
300340

301341
// Generic class special rules
302342
// Validate that a generic class cannot be a ComImport class, or a ComEventInterface class -- UNIMPLEMENTED
303-
// Validate that there are no cyclical class or method constraints, and that constraints are all acccessible to the type using them -- UNIMPLEMENTED
343+
344+
foreach (var genericParameterType in type.Instantiation)
345+
{
346+
foreach (var constraint in ((GenericParameterDesc)genericParameterType).TypeConstraints)
347+
{
348+
if (!ValidateVarianceInType(type.Instantiation, constraint, Internal.TypeSystem.GenericVariance.Contravariant))
349+
{
350+
AddTypeValidationError(type, $"'{genericParameterType}' has invalid variance in generic parameter constraint {constraint}");
351+
return false;
352+
}
353+
}
354+
}
355+
356+
// Validate that generic variance is properly respected in interface signatures
357+
if (type.HasInstantiation && type.IsInterface)
358+
{
359+
foreach (var interfaceType in type.ExplicitlyImplementedInterfaces)
360+
{
361+
if (interfaceType.HasInstantiation)
362+
{
363+
// Interfaces behave covariantly
364+
if (!ValidateVarianceInType(type.Instantiation, interfaceType, Internal.TypeSystem.GenericVariance.Covariant))
365+
{
366+
AddTypeValidationError(type, $"'{type}' has invalid variance in in interface impl of {interfaceType}");
367+
return false;
368+
}
369+
}
370+
}
371+
}
372+
373+
// Validate that there are no cyclical class or method constraints, and that constraints are all acccessible to the type using them
374+
if (!BoundedCheckForInstantiation(type.Instantiation))
375+
{
376+
AddTypeValidationError(type, $"'{type}' has cyclical generic parameter constraints");
377+
return false;
378+
}
304379

305380
// Override rules
306381
// Validate that each override results does not violate accessibility rules -- UNIMPLEMENTED
@@ -579,6 +654,114 @@ async Task<bool> ValidateTypeHelperFunctionPointerType(FunctionPointerType funct
579654
}
580655
return true;
581656
}
657+
658+
static bool BoundedCheckForInstantiation(Instantiation instantiation)
659+
{
660+
foreach (var type in instantiation)
661+
{
662+
Debug.Assert(type.IsGenericParameter);
663+
GenericParameterDesc genericParameter = (GenericParameterDesc)type;
664+
665+
if (!BoundedCheckForType(genericParameter, instantiation.Length))
666+
return false;
667+
}
668+
return true;
669+
}
670+
671+
static bool BoundedCheckForType(GenericParameterDesc genericParameter, int maxDepth)
672+
{
673+
if (maxDepth <= 0)
674+
return false;
675+
foreach (var type in genericParameter.TypeConstraints)
676+
{
677+
if (type is GenericParameterDesc possiblyCircularGenericParameter)
678+
{
679+
if (possiblyCircularGenericParameter.Kind == genericParameter.Kind)
680+
{
681+
if (!BoundedCheckForType(possiblyCircularGenericParameter, maxDepth - 1))
682+
return false;
683+
}
684+
}
685+
}
686+
return true;
687+
}
688+
689+
static bool ValidateVarianceInSignature(Instantiation associatedTypeInstantiation, MethodSignature signature, Internal.TypeSystem.GenericVariance returnTypePosition, Internal.TypeSystem.GenericVariance argTypePosition)
690+
{
691+
for (int i = 0; i < signature.Length; i++)
692+
{
693+
if (!ValidateVarianceInType(associatedTypeInstantiation, signature[i], argTypePosition))
694+
return false;
695+
}
696+
697+
if (!ValidateVarianceInType(associatedTypeInstantiation, signature.ReturnType, returnTypePosition))
698+
return false;
699+
700+
return true;
701+
}
702+
703+
static bool ValidateVarianceInType(Instantiation associatedTypeInstantiation, TypeDesc type, Internal.TypeSystem.GenericVariance position)
704+
{
705+
if (type is SignatureTypeVariable signatureTypeVar)
706+
{
707+
GenericParameterDesc gp = (GenericParameterDesc)associatedTypeInstantiation[signatureTypeVar.Index];
708+
if (gp.Variance != Internal.TypeSystem.GenericVariance.None)
709+
{
710+
if (position != gp.Variance)
711+
{
712+
// Covariant and contravariant parameters can *only* appear in resp. covariant and contravariant positions
713+
return false;
714+
}
715+
}
716+
}
717+
else if (type is InstantiatedType instantiatedType)
718+
{
719+
var typeDef = instantiatedType.GetTypeDefinition();
720+
if (typeDef.IsValueType || position == Internal.TypeSystem.GenericVariance.None)
721+
{
722+
// Value types and non-variant positions require all parameters to be non-variant
723+
foreach (TypeDesc instType in instantiatedType.Instantiation)
724+
{
725+
if (!ValidateVarianceInType(associatedTypeInstantiation, instType, Internal.TypeSystem.GenericVariance.None))
726+
return false;
727+
}
728+
}
729+
else
730+
{
731+
int index = 0;
732+
for (index = 0; index < typeDef.Instantiation.Length; index++)
733+
{
734+
Internal.TypeSystem.GenericVariance positionForParameter = ((GenericParameterDesc)typeDef.Instantiation[index]).Variance;
735+
// If the surrounding context is contravariant then we need to flip the variance of this parameter
736+
if (position == Internal.TypeSystem.GenericVariance.Contravariant)
737+
{
738+
if (positionForParameter == Internal.TypeSystem.GenericVariance.Covariant)
739+
positionForParameter = Internal.TypeSystem.GenericVariance.Contravariant;
740+
else if (positionForParameter == Internal.TypeSystem.GenericVariance.Contravariant)
741+
positionForParameter = Internal.TypeSystem.GenericVariance.Covariant;
742+
else
743+
positionForParameter = Internal.TypeSystem.GenericVariance.None;
744+
}
745+
746+
if (!ValidateVarianceInType(associatedTypeInstantiation, instantiatedType.Instantiation[index], positionForParameter))
747+
return false;
748+
}
749+
}
750+
}
751+
else if (type is ParameterizedType parameterizedType)
752+
{
753+
// Arrays behave as covariant, byref and pointer types are non-variant
754+
if (!ValidateVarianceInType(associatedTypeInstantiation, parameterizedType.ParameterType, (parameterizedType.IsByRef || parameterizedType.IsPointer) ? Internal.TypeSystem.GenericVariance.None : position))
755+
return false;
756+
}
757+
else if (type is FunctionPointerType functionPointerType)
758+
{
759+
if (!ValidateVarianceInSignature(associatedTypeInstantiation, functionPointerType.Signature, Internal.TypeSystem.GenericVariance.None, Internal.TypeSystem.GenericVariance.None))
760+
return false;
761+
}
762+
763+
return true;
764+
}
582765
}
583766
}
584767
}

src/coreclr/vm/ceeload.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -434,10 +434,16 @@ void Module::Initialize(AllocMemTracker *pamTracker, LPCWSTR szName)
434434
AllocateMaps();
435435
m_dwTransientFlags &= ~((DWORD)CLASSES_FREED); // Set flag indicating LookupMaps are now in a consistent and destructable state
436436

437+
if (IsSystem())
438+
m_dwPersistedFlags |= SKIP_TYPE_VALIDATION; // Skip type validation on System
439+
437440
#ifdef FEATURE_READYTORUN
438441
m_pNativeImage = NULL;
439442
if ((m_pReadyToRunInfo = ReadyToRunInfo::Initialize(this, pamTracker)) != NULL)
440443
{
444+
if (m_pReadyToRunInfo->SkipTypeValidation())
445+
m_dwPersistedFlags |= SKIP_TYPE_VALIDATION; // Skip type validation on System
446+
441447
m_pNativeImage = m_pReadyToRunInfo->GetNativeImage();
442448
if (m_pNativeImage != NULL)
443449
{

src/coreclr/vm/ceeload.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -686,6 +686,8 @@ class Module : public ModuleBase
686686
RUNTIME_MARSHALLING_ENABLED_IS_CACHED = 0x00008000,
687687
//If runtime marshalling is enabled for this assembly
688688
RUNTIME_MARSHALLING_ENABLED = 0x00010000,
689+
690+
SKIP_TYPE_VALIDATION = 0x00020000,
689691
};
690692

691693
Volatile<DWORD> m_dwTransientFlags;
@@ -1500,6 +1502,12 @@ class Module : public ModuleBase
15001502
#endif
15011503
}
15021504

1505+
bool SkipTypeValidation() const
1506+
{
1507+
LIMITED_METHOD_DAC_CONTRACT;
1508+
1509+
return (m_dwPersistedFlags & SKIP_TYPE_VALIDATION) != 0;
1510+
}
15031511
#ifdef FEATURE_READYTORUN
15041512
PTR_ReadyToRunInfo GetReadyToRunInfo() const
15051513
{

src/coreclr/vm/class.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -941,7 +941,7 @@ EEClass::CheckVarianceInSig(
941941
uint32_t cArgs;
942942
IfFailThrow(psig.GetData(&cArgs));
943943

944-
// Conservatively, assume non-variance of function pointer types
944+
// Conservatively, assume non-variance of function pointer types, if we ever change this, update the TypeValidationChecker in crossgen2 also
945945
if (!CheckVarianceInSig(numGenericArgs, pVarianceInfo, pModule, psig, gpNonVariant))
946946
return FALSE;
947947

@@ -1359,7 +1359,7 @@ void ClassLoader::ValidateMethodsWithCovariantReturnTypes(MethodTable* pMT)
13591359
return;
13601360

13611361
// Step 1: Validate compatibility of return types on overriding methods
1362-
if (pMT->GetClass()->HasCovariantOverride() && (!pMT->GetModule()->IsReadyToRun() || !pMT->GetModule()->GetReadyToRunInfo()->SkipTypeValidation()))
1362+
if (pMT->GetClass()->HasCovariantOverride() && !pMT->GetModule()->SkipTypeValidation())
13631363
{
13641364
for (WORD i = 0; i < pParentMT->GetNumVirtuals(); i++)
13651365
{

src/coreclr/vm/comdelegate.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2403,16 +2403,16 @@ static bool IsLocationAssignable(TypeHandle fromHandle, TypeHandle toHandle, boo
24032403
// We need to check whether constraints of fromHandle have been loaded, because the
24042404
// CanCastTo operation might have made its decision without enumerating constraints
24052405
// (e.g. when toHandle is System.Object).
2406-
if (!fromHandleVar->ConstraintsLoaded())
2407-
fromHandleVar->LoadConstraints(CLASS_DEPENDENCIES_LOADED);
2406+
if (!fromHandleVar->ConstraintsLoaded(WhichConstraintsToLoad::TypeOrMethodVarsAndNonInterfacesOnly))
2407+
fromHandleVar->LoadConstraints(CLASS_DEPENDENCIES_LOADED, WhichConstraintsToLoad::TypeOrMethodVarsAndNonInterfacesOnly);
24082408

24092409
if (toHandle.IsGenericVariable())
24102410
{
24112411
TypeVarTypeDesc *toHandleVar = toHandle.AsGenericVariable();
24122412

24132413
// Constraints of toHandleVar were not touched by CanCastTo.
2414-
if (!toHandleVar->ConstraintsLoaded())
2415-
toHandleVar->LoadConstraints(CLASS_DEPENDENCIES_LOADED);
2414+
if (!toHandleVar->ConstraintsLoaded(WhichConstraintsToLoad::TypeOrMethodVarsAndNonInterfacesOnly))
2415+
toHandleVar->LoadConstraints(CLASS_DEPENDENCIES_LOADED, WhichConstraintsToLoad::TypeOrMethodVarsAndNonInterfacesOnly);
24162416

24172417
// Both handles are type variables. The following table lists all possible combinations.
24182418
//

src/coreclr/vm/genmeth.cpp

Lines changed: 11 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1536,7 +1536,7 @@ BOOL Bounded(TypeVarTypeDesc *tyvar, DWORD depth) {
15361536
}
15371537

15381538
DWORD numConstraints;
1539-
TypeHandle *constraints = tyvar->GetConstraints(&numConstraints, CLASS_DEPENDENCIES_LOADED);
1539+
TypeHandle *constraints = tyvar->GetConstraints(&numConstraints, CLASS_DEPENDENCIES_LOADED, WhichConstraintsToLoad::TypeOrMethodVarsAndNonInterfacesOnly);
15401540
for (unsigned i = 0; i < numConstraints; i++)
15411541
{
15421542
TypeHandle constraint = constraints[i];
@@ -1554,56 +1554,31 @@ BOOL Bounded(TypeVarTypeDesc *tyvar, DWORD depth) {
15541554
return TRUE;
15551555
}
15561556

1557-
void MethodDesc::LoadConstraintsForTypicalMethodDefinition(BOOL *pfHasCircularClassConstraints, BOOL *pfHasCircularMethodConstraints, ClassLoadLevel level/* = CLASS_LOADED*/)
1557+
void MethodDesc::CheckConstraintMetadataValidity(BOOL *pfHasCircularMethodConstraints)
15581558
{
15591559
CONTRACTL {
1560-
THROWS;
1561-
GC_TRIGGERS;
1562-
MODE_ANY;
1560+
STANDARD_VM_CHECK;
15631561
PRECONDITION(IsTypicalMethodDefinition());
1564-
PRECONDITION(CheckPointer(pfHasCircularClassConstraints));
15651562
PRECONDITION(CheckPointer(pfHasCircularMethodConstraints));
15661563
} CONTRACTL_END;
15671564

1568-
*pfHasCircularClassConstraints = FALSE;
1565+
// In this function we explicitly check for accessibility of method type parameter constraints as
1566+
// well as explicitly do a check for circularity among method type parameter constraints.
1567+
//
1568+
// For checking the variance of the constraints we rely on the fact that both DoAccessibilityCheckForConstraints
1569+
// and Bounded will call GetConstraints on the type variables, which will in turn call
1570+
// LoadConstraints, and LoadConstraints will do the variance checking using EEClass::CheckVarianceInSig
15691571
*pfHasCircularMethodConstraints = FALSE;
15701572

1571-
// Force a load of the constraints on the type parameters
1572-
Instantiation classInst = GetClassInstantiation();
1573-
for (DWORD i = 0; i < classInst.GetNumArgs(); i++)
1574-
{
1575-
TypeVarTypeDesc* tyvar = classInst[i].AsGenericVariable();
1576-
_ASSERTE(tyvar != NULL);
1577-
tyvar->LoadConstraints(level);
1578-
}
1579-
15801573
Instantiation methodInst = GetMethodInstantiation();
1581-
for (DWORD i = 0; i < methodInst.GetNumArgs(); i++)
1582-
{
1583-
TypeVarTypeDesc* tyvar = methodInst[i].AsGenericVariable();
1584-
_ASSERTE(tyvar != NULL);
1585-
tyvar->LoadConstraints(level);
1586-
1587-
VOID DoAccessibilityCheckForConstraints(MethodTable *pAskingMT, TypeVarTypeDesc *pTyVar, UINT resIDWhy);
1588-
DoAccessibilityCheckForConstraints(GetMethodTable(), tyvar, E_ACCESSDENIED);
1589-
}
1590-
1591-
// reject circular class constraints
1592-
for (DWORD i = 0; i < classInst.GetNumArgs(); i++)
1593-
{
1594-
TypeVarTypeDesc* tyvar = classInst[i].AsGenericVariable();
1595-
_ASSERTE(tyvar != NULL);
1596-
if(!Bounded(tyvar, classInst.GetNumArgs()))
1597-
{
1598-
*pfHasCircularClassConstraints = TRUE;
1599-
}
1600-
}
16011574

16021575
// reject circular method constraints
16031576
for (DWORD i = 0; i < methodInst.GetNumArgs(); i++)
16041577
{
16051578
TypeVarTypeDesc* tyvar = methodInst[i].AsGenericVariable();
16061579
_ASSERTE(tyvar != NULL);
1580+
VOID DoAccessibilityCheckForConstraints(MethodTable *pAskingMT, TypeVarTypeDesc *pTyVar, UINT resIDWhy);
1581+
DoAccessibilityCheckForConstraints(GetMethodTable(), tyvar, E_ACCESSDENIED);
16071582
if(!Bounded(tyvar, methodInst.GetNumArgs()))
16081583
{
16091584
*pfHasCircularMethodConstraints = TRUE;
@@ -1662,8 +1637,6 @@ BOOL MethodDesc::SatisfiesMethodConstraints(TypeHandle thParent, BOOL fThrowIfNo
16621637
_ASSERTE(tyvar != NULL);
16631638
_ASSERTE(TypeFromToken(tyvar->GetTypeOrMethodDef()) == mdtMethodDef);
16641639

1665-
tyvar->LoadConstraints(); //TODO: is this necessary for anything but the typical method?
1666-
16671640
// Pass in the InstatiationContext so constraints can be correctly evaluated
16681641
// if this is an instantiation where the type variable is in its open position
16691642
if (!tyvar->SatisfiesConstraints(&typeContext,thArg, typicalInstMatchesMethodInst ? &instContext : NULL))

0 commit comments

Comments
 (0)