diff --git a/eng/Subsets.props b/eng/Subsets.props
index 05470bc9aae954..1fb58e81239a9a 100644
--- a/eng/Subsets.props
+++ b/eng/Subsets.props
@@ -246,6 +246,8 @@
+
diff --git a/eng/pipelines/coreclr/templates/build-job.yml b/eng/pipelines/coreclr/templates/build-job.yml
index a8c57eadac6255..b89cd621eda1ee 100644
--- a/eng/pipelines/coreclr/templates/build-job.yml
+++ b/eng/pipelines/coreclr/templates/build-job.yml
@@ -132,7 +132,7 @@ jobs:
value: ''
- ${{ if ne(parameters.testGroup, 'innerloop') }}:
- name: clrRuntimeComponentsBuildArg
- value: '-component runtime -component alljits -component paltests '
+ value: '-component runtime -component alljits -component paltests -component nativeaot '
- ${{ if and(eq(parameters.osGroup, 'Linux'), eq(parameters.archType, 'x86')) }}:
- name: clrRuntimeComponentsBuildArg
value: '-component runtime -component jit -component iltools '
diff --git a/src/coreclr/nativeaot/Test.CoreLib/src/Test.CoreLib.csproj b/src/coreclr/nativeaot/Test.CoreLib/src/Test.CoreLib.csproj
index c95cc9c17b8a39..29a54764b2f69b 100644
--- a/src/coreclr/nativeaot/Test.CoreLib/src/Test.CoreLib.csproj
+++ b/src/coreclr/nativeaot/Test.CoreLib/src/Test.CoreLib.csproj
@@ -2,6 +2,7 @@
false
false
+ netstandard2.0
FEATURE_GC_STRESS;$(DefineConstants)
diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/DependencyGraphTests.cs b/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/DependencyGraphTests.cs
new file mode 100644
index 00000000000000..954bb7d8b9c6f9
--- /dev/null
+++ b/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/DependencyGraphTests.cs
@@ -0,0 +1,135 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Collections.Generic;
+
+using Internal.IL;
+using Internal.TypeSystem;
+using Internal.TypeSystem.Ecma;
+
+using Xunit;
+
+using CustomAttributeValue = System.Reflection.Metadata.CustomAttributeValue;
+
+namespace ILCompiler.Compiler.Tests
+{
+ //
+ // This test uses IL scanner to scan a dependency graph, starting with a
+ // single method from the test assembly.
+ // It then checks various invariants about the resulting dependency graph.
+ // The test method declares these invariants using custom attributes.
+ //
+ // The invariants to check for are:
+ // * Whether an EEType was/was not generated
+ // * Whether a method body was/was not generated
+ // * Etc.
+ //
+ // The most valuable tests are the ones that check that something was not
+ // generated. These let us create unit tests for size on disk regressions.
+ //
+
+ public class DependencyGraphTests
+ {
+ public static IEnumerable GetTestMethods()
+ {
+ var target = new TargetDetails(TargetArchitecture.X64, TargetOS.Windows, TargetAbi.CoreRT);
+ var context = new CompilerTypeSystemContext(target, SharedGenericsMode.CanonicalReferenceTypes, DelegateFeature.All);
+
+ context.InputFilePaths = new Dictionary {
+ { "Test.CoreLib", @"Test.CoreLib.dll" },
+ { "ILCompiler.Compiler.Tests.Assets", @"ILCompiler.Compiler.Tests.Assets.dll" },
+ };
+ context.ReferenceFilePaths = new Dictionary();
+
+ context.SetSystemModule(context.GetModuleForSimpleName("Test.CoreLib"));
+ var testModule = context.GetModuleForSimpleName("ILCompiler.Compiler.Tests.Assets");
+
+ bool foundSomethingToCheck = false;
+ foreach (var type in testModule.GetType("ILCompiler.Compiler.Tests.Assets", "DependencyGraph").GetNestedTypes())
+ {
+ foundSomethingToCheck = true;
+ yield return new object[] { type.GetMethod("Entrypoint", null) };
+ }
+
+ Assert.True(foundSomethingToCheck, "No methods to check?");
+ }
+
+ [Theory]
+ [MemberData(nameof(GetTestMethods))]
+ public void TestDependencyGraphInvariants(EcmaMethod method)
+ {
+ //
+ // Scan the input method
+ //
+
+ var context = (CompilerTypeSystemContext)method.Context;
+ CompilationModuleGroup compilationGroup = new SingleFileCompilationModuleGroup();
+
+ CoreRTILProvider ilProvider = new CoreRTILProvider();
+
+ UsageBasedMetadataManager metadataManager = new UsageBasedMetadataManager(compilationGroup, context,
+ new FullyBlockedMetadataBlockingPolicy(), new FullyBlockedManifestResourceBlockingPolicy(),
+ null, new NoStackTraceEmissionPolicy(), new NoDynamicInvokeThunkGenerationPolicy(),
+ new Dataflow.FlowAnnotations(Logger.Null, ilProvider), UsageBasedMetadataGenerationOptions.None,
+ Logger.Null, Array.Empty>(), Array.Empty(), Array.Empty());
+
+ CompilationBuilder builder = new RyuJitCompilationBuilder(context, compilationGroup)
+ .UseILProvider(ilProvider);
+
+ IILScanner scanner = builder.GetILScannerBuilder()
+ .UseCompilationRoots(new ICompilationRootProvider[] { new SingleMethodRootProvider(method) })
+ .UseMetadataManager(metadataManager)
+ .ToILScanner();
+
+ ILScanResults results = scanner.Scan();
+
+ //
+ // Check invariants
+ //
+
+ const string assetsNamespace = "ILCompiler.Compiler.Tests.Assets";
+ bool foundSomethingToCheck = false;
+
+ foreach (var attr in method.GetDecodedCustomAttributes(assetsNamespace, "GeneratesConstructedEETypeAttribute"))
+ {
+ foundSomethingToCheck = true;
+ Assert.Contains((TypeDesc)attr.FixedArguments[0].Value, results.ConstructedEETypes);
+ }
+
+ foreach (var attr in method.GetDecodedCustomAttributes(assetsNamespace, "NoConstructedEETypeAttribute"))
+ {
+ foundSomethingToCheck = true;
+ Assert.DoesNotContain((TypeDesc)attr.FixedArguments[0].Value, results.ConstructedEETypes);
+ }
+
+ foreach (var attr in method.GetDecodedCustomAttributes(assetsNamespace, "GeneratesMethodBodyAttribute"))
+ {
+ foundSomethingToCheck = true;
+ MethodDesc methodToCheck = GetMethodFromAttribute(attr);
+ Assert.Contains(methodToCheck.GetCanonMethodTarget(CanonicalFormKind.Specific), results.CompiledMethodBodies);
+ }
+
+ foreach (var attr in method.GetDecodedCustomAttributes(assetsNamespace, "NoMethodBodyAttribute"))
+ {
+ foundSomethingToCheck = true;
+ MethodDesc methodToCheck = GetMethodFromAttribute(attr);
+ Assert.DoesNotContain(methodToCheck.GetCanonMethodTarget(CanonicalFormKind.Specific), results.CompiledMethodBodies);
+ }
+
+ //
+ // Make sure we checked something
+ //
+
+ Assert.True(foundSomethingToCheck, "No invariants to check?");
+ }
+
+ private static MethodDesc GetMethodFromAttribute(CustomAttributeValue attr)
+ {
+ if (attr.NamedArguments.Length > 0)
+ throw new NotImplementedException(); // TODO: parse sig and instantiation
+
+ return ((TypeDesc)attr.FixedArguments[0].Value).GetMethod((string)attr.FixedArguments[1].Value, null);
+ }
+ }
+}
diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/DevirtualizationTests.cs b/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/DevirtualizationTests.cs
new file mode 100644
index 00000000000000..37a0436e468ca1
--- /dev/null
+++ b/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/DevirtualizationTests.cs
@@ -0,0 +1,70 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections.Generic;
+
+using Internal.IL;
+using Internal.TypeSystem;
+
+using Xunit;
+
+namespace ILCompiler.Compiler.Tests
+{
+ public class DevirtualizationTests
+ {
+ private readonly CompilerTypeSystemContext _context;
+ private readonly ModuleDesc _testModule;
+
+ public DevirtualizationTests()
+ {
+ var target = new TargetDetails(TargetArchitecture.X64, TargetOS.Windows, TargetAbi.CoreRT);
+ _context = new CompilerTypeSystemContext(target, SharedGenericsMode.CanonicalReferenceTypes, DelegateFeature.All);
+
+ _context.InputFilePaths = new Dictionary {
+ { "Test.CoreLib", @"Test.CoreLib.dll" },
+ { "ILCompiler.Compiler.Tests.Assets", @"ILCompiler.Compiler.Tests.Assets.dll" },
+ };
+ _context.ReferenceFilePaths = new Dictionary();
+
+ _context.SetSystemModule(_context.GetModuleForSimpleName("Test.CoreLib"));
+ _testModule = _context.GetModuleForSimpleName("ILCompiler.Compiler.Tests.Assets");
+ }
+
+ private DevirtualizationManager GetDevirtualizationManagerFromScan(MethodDesc method)
+ {
+ CompilationModuleGroup compilationGroup = new SingleFileCompilationModuleGroup();
+
+ CompilationBuilder builder = new RyuJitCompilationBuilder(_context, compilationGroup);
+ IILScanner scanner = builder.GetILScannerBuilder()
+ .UseCompilationRoots(new ICompilationRootProvider[] { new SingleMethodRootProvider(method) })
+ .ToILScanner();
+
+ return scanner.Scan().GetDevirtualizationManager();
+ }
+
+ [Fact]
+ public void TestDevirtualizeSimple()
+ {
+ MetadataType testType = _testModule.GetType("Devirtualization", "DevirtualizeSimple");
+ DevirtualizationManager scanDevirt = GetDevirtualizationManagerFromScan(testType.GetMethod("Run", null));
+
+ MethodDesc implMethod = testType.GetNestedType("Derived").GetMethod("Virtual", null);
+
+ // The impl method should be treated as sealed
+ Assert.True(scanDevirt.IsEffectivelySealed(implMethod));
+
+ // Even though the metadata based algorithm would say it isn't
+ var devirt = new DevirtualizationManager();
+ Assert.False(devirt.IsEffectivelySealed(implMethod));
+ }
+
+ [Fact]
+ public void TestDevirtualizeAbstract()
+ {
+ MetadataType testType = _testModule.GetType("Devirtualization", "DevirtualizeAbstract");
+ DevirtualizationManager scanDevirt = GetDevirtualizationManagerFromScan(testType.GetMethod("Run", null));
+
+ Assert.False(scanDevirt.IsEffectivelySealed(testType.GetNestedType("Abstract")));
+ }
+ }
+}
diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/ILCompiler.Compiler.Tests.Assets/DependencyGraph.cs b/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/ILCompiler.Compiler.Tests.Assets/DependencyGraph.cs
new file mode 100644
index 00000000000000..ef2fdab5b8b790
--- /dev/null
+++ b/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/ILCompiler.Compiler.Tests.Assets/DependencyGraph.cs
@@ -0,0 +1,96 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Runtime.InteropServices;
+
+namespace ILCompiler.Compiler.Tests.Assets
+{
+ //
+ // Classes nested under this class gets automatically discovered by the unit test.
+ // The unit test will locate the Entrypoint method, run the IL scanner on it,
+ // and validate the invariants declared by the Entrypoint method with custom attributes.
+ //
+
+ class DependencyGraph
+ {
+ ///
+ /// Validates a cast doesn't force a constructed EEType.
+ ///
+ class PInvokeCctorDependencyTest
+ {
+ class TypeThatWasNeverAllocated
+ {
+ public static object O = null;
+ }
+
+ [NoConstructedEEType(typeof(TypeThatWasNeverAllocated))]
+ public static void Entrypoint()
+ {
+ ((TypeThatWasNeverAllocated)TypeThatWasNeverAllocated.O).GetHashCode();
+ }
+ }
+
+ class GenericVirtualMethodDirectCallDependencyTest
+ {
+ class NeverAllocated { }
+
+ class Base
+ {
+ public virtual object GenericVirtualCalledDirectly()
+ {
+ return null;
+ }
+ }
+
+ class Derived : Base
+ {
+ public override object GenericVirtualCalledDirectly()
+ {
+ return new NeverAllocated();
+ }
+
+ public object CallBaseGenericVirtualDirectly()
+ {
+ // This is a call in IL, not callvirt
+ return base.GenericVirtualCalledDirectly();
+ }
+ }
+
+ [NoConstructedEEType(typeof(NeverAllocated))]
+ public static void Entrypoint()
+ {
+ new Base();
+ new Derived().CallBaseGenericVirtualDirectly();
+ }
+ }
+ }
+
+ #region Custom attributes that define invariants to check
+ public class GeneratesConstructedEETypeAttribute : Attribute
+ {
+ public GeneratesConstructedEETypeAttribute(Type type) { }
+ }
+
+ public class NoConstructedEETypeAttribute : Attribute
+ {
+ public NoConstructedEETypeAttribute(Type type) { }
+ }
+
+ public class GeneratesMethodBodyAttribute : Attribute
+ {
+ public GeneratesMethodBodyAttribute(Type owningType, string methodName) { }
+
+ public Type[] GenericArguments;
+ public Type[] Signature;
+ }
+
+ public class NoMethodBodyAttribute : Attribute
+ {
+ public NoMethodBodyAttribute(Type owningType, string methodName) { }
+
+ public Type[] GenericArguments;
+ public Type[] Signature;
+ }
+ #endregion
+}
diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/ILCompiler.Compiler.Tests.Assets/Devirtualization.cs b/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/ILCompiler.Compiler.Tests.Assets/Devirtualization.cs
new file mode 100644
index 00000000000000..a1c894bd6a8133
--- /dev/null
+++ b/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/ILCompiler.Compiler.Tests.Assets/Devirtualization.cs
@@ -0,0 +1,37 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace Devirtualization
+{
+ class DevirtualizeSimple
+ {
+ abstract class Base
+ {
+ public abstract void Virtual();
+ }
+
+ class Derived : Base
+ {
+ public override void Virtual()
+ {
+ new Derived();
+ }
+ }
+
+ static void Run()
+ {
+ Base p = new Derived();
+ p.Virtual();
+ }
+ }
+
+ class DevirtualizeAbstract
+ {
+ abstract class Abstract { }
+
+ static void Run()
+ {
+ typeof(Abstract).GetHashCode();
+ }
+ }
+}
diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/ILCompiler.Compiler.Tests.Assets/ILCompiler.Compiler.Tests.Assets.csproj b/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/ILCompiler.Compiler.Tests.Assets/ILCompiler.Compiler.Tests.Assets.csproj
new file mode 100644
index 00000000000000..ea167e092b0318
--- /dev/null
+++ b/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/ILCompiler.Compiler.Tests.Assets/ILCompiler.Compiler.Tests.Assets.csproj
@@ -0,0 +1,17 @@
+
+
+ Library
+ ILCompiler.Compiler.Tests.Assets
+ false
+ false
+ true
+ netstandard2.0
+
+ true
+
+
+
+
+
+
+
diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/ILCompiler.Compiler.Tests.csproj b/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/ILCompiler.Compiler.Tests.csproj
new file mode 100644
index 00000000000000..5967b093841b15
--- /dev/null
+++ b/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/ILCompiler.Compiler.Tests.csproj
@@ -0,0 +1,47 @@
+
+
+ ILCompiler.Compiler.Tests
+ $(NetCoreAppToolCurrent)
+ Debug;Release;Checked
+
+ true
+ -notrait category=failing
+
+ $(NoWarn);NU1701
+
+ false
+ x86;x64
+ AnyCPU
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ false
+ Content
+ PreserveNewest
+
+
+ false
+ Content
+ PreserveNewest
+
+
+
+
+
+
+
+
diff --git a/src/coreclr/tools/aot/ilc.sln b/src/coreclr/tools/aot/ilc.sln
index 225476433362ff..be90b9bc202d73 100644
--- a/src/coreclr/tools/aot/ilc.sln
+++ b/src/coreclr/tools/aot/ilc.sln
@@ -18,6 +18,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "repro", "ILCompiler\repro\r
EndProject
Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "ILLink.Shared", "ILLink.Shared\ILLink.Shared.shproj", "{FF598E93-8E9E-4091-9F50-61A7572663AE}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ILCompiler.Compiler.Tests", "ILCompiler.Compiler.Tests\ILCompiler.Compiler.Tests.csproj", "{24CBA9C6-EDBA-47D6-A0B5-04417BDE5FE3}"
+EndProject
Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution
ILLink.Shared\ILLink.Shared.projitems*{ff598e93-8e9e-4091-9f50-61a7572663ae}*SharedItemsImports = 13
@@ -140,6 +142,24 @@ Global
{CBDE0470-E0C9-4693-9A11-ACC117522F3F}.Release|x64.Build.0 = Release|x64
{CBDE0470-E0C9-4693-9A11-ACC117522F3F}.Release|x86.ActiveCfg = Release|x86
{CBDE0470-E0C9-4693-9A11-ACC117522F3F}.Release|x86.Build.0 = Release|x86
+ {24CBA9C6-EDBA-47D6-A0B5-04417BDE5FE3}.Checked|Any CPU.ActiveCfg = Checked|x64
+ {24CBA9C6-EDBA-47D6-A0B5-04417BDE5FE3}.Checked|Any CPU.Build.0 = Checked|x64
+ {24CBA9C6-EDBA-47D6-A0B5-04417BDE5FE3}.Checked|x64.ActiveCfg = Checked|x64
+ {24CBA9C6-EDBA-47D6-A0B5-04417BDE5FE3}.Checked|x64.Build.0 = Checked|x64
+ {24CBA9C6-EDBA-47D6-A0B5-04417BDE5FE3}.Checked|x86.ActiveCfg = Checked|x86
+ {24CBA9C6-EDBA-47D6-A0B5-04417BDE5FE3}.Checked|x86.Build.0 = Checked|x86
+ {24CBA9C6-EDBA-47D6-A0B5-04417BDE5FE3}.Debug|Any CPU.ActiveCfg = Debug|x64
+ {24CBA9C6-EDBA-47D6-A0B5-04417BDE5FE3}.Debug|Any CPU.Build.0 = Debug|x64
+ {24CBA9C6-EDBA-47D6-A0B5-04417BDE5FE3}.Debug|x64.ActiveCfg = Debug|x64
+ {24CBA9C6-EDBA-47D6-A0B5-04417BDE5FE3}.Debug|x64.Build.0 = Debug|x64
+ {24CBA9C6-EDBA-47D6-A0B5-04417BDE5FE3}.Debug|x86.ActiveCfg = Debug|x86
+ {24CBA9C6-EDBA-47D6-A0B5-04417BDE5FE3}.Debug|x86.Build.0 = Debug|x86
+ {24CBA9C6-EDBA-47D6-A0B5-04417BDE5FE3}.Release|Any CPU.ActiveCfg = Release|x64
+ {24CBA9C6-EDBA-47D6-A0B5-04417BDE5FE3}.Release|Any CPU.Build.0 = Release|x64
+ {24CBA9C6-EDBA-47D6-A0B5-04417BDE5FE3}.Release|x64.ActiveCfg = Release|x64
+ {24CBA9C6-EDBA-47D6-A0B5-04417BDE5FE3}.Release|x64.Build.0 = Release|x64
+ {24CBA9C6-EDBA-47D6-A0B5-04417BDE5FE3}.Release|x86.ActiveCfg = Release|x86
+ {24CBA9C6-EDBA-47D6-A0B5-04417BDE5FE3}.Release|x86.Build.0 = Release|x86
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE