Build header tables using metadata manager (#6222)
* Build header tables using metadata manager * Enable MetadataManager to track use of types / methods * Introduce `AvailableType` which replaces the CoreRT EETypeNode. Ready-to-run doesn't emit types directly into the image but records their use and fills a table of available types used for type resolution between ready-to-run modules by the runtime. * Add dependency tracking in a few existing places where references to types are being made. In library compilation mode, the non-private types will be rooted so they always end up in the available types table. * Use a single NodeCache for method entrypoints instead of a separate dictionary for ready-to-run entrypoints. To smooth over the API differences between the base `NodeFactory` and `ReadyToRunCodegenNodeFactory`, allow a custom GetOrAdd method that takes a creator callback. * Switch over `RuntimeFunctionsTable`, `TypesTable`, `MethodEntryPointTable` to use metadata manager for the list of types / methods to emit entries for.
This commit is contained in:
Родитель
ee5bc9f6ff
Коммит
3d56b11db6
|
@ -149,60 +149,18 @@ namespace ILCompiler.DependencyAnalysis
|
||||||
{
|
{
|
||||||
return _cache.GetOrAdd(key, _creator);
|
return _cache.GetOrAdd(key, _creator);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public TValue GetOrAdd(TKey key, Func<TKey, TValue> creator)
|
||||||
|
{
|
||||||
|
return _cache.GetOrAdd(key, creator);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CreateNodeCaches()
|
private void CreateNodeCaches()
|
||||||
{
|
{
|
||||||
_typeSymbols = new NodeCache<TypeDesc, IEETypeNode>((TypeDesc type) =>
|
_typeSymbols = new NodeCache<TypeDesc, IEETypeNode>(CreateNecessaryTypeNode);
|
||||||
{
|
|
||||||
Debug.Assert(!_compilationModuleGroup.ShouldReferenceThroughImportTable(type));
|
|
||||||
if (_compilationModuleGroup.ContainsType(type))
|
|
||||||
{
|
|
||||||
if (type.IsGenericDefinition)
|
|
||||||
{
|
|
||||||
return new GenericDefinitionEETypeNode(this, type);
|
|
||||||
}
|
|
||||||
else if (type.IsCanonicalDefinitionType(CanonicalFormKind.Any))
|
|
||||||
{
|
|
||||||
return new CanonicalDefinitionEETypeNode(this, type);
|
|
||||||
}
|
|
||||||
else if (type.IsCanonicalSubtype(CanonicalFormKind.Any))
|
|
||||||
{
|
|
||||||
return new NecessaryCanonicalEETypeNode(this, type);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return new EETypeNode(this, type);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return new ExternEETypeSymbolNode(this, type);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
_constructedTypeSymbols = new NodeCache<TypeDesc, IEETypeNode>((TypeDesc type) =>
|
_constructedTypeSymbols = new NodeCache<TypeDesc, IEETypeNode>(CreateConstructedTypeNode);
|
||||||
{
|
|
||||||
// Canonical definition types are *not* constructed types (call NecessaryTypeSymbol to get them)
|
|
||||||
Debug.Assert(!type.IsCanonicalDefinitionType(CanonicalFormKind.Any));
|
|
||||||
Debug.Assert(!_compilationModuleGroup.ShouldReferenceThroughImportTable(type));
|
|
||||||
|
|
||||||
if (_compilationModuleGroup.ContainsType(type))
|
|
||||||
{
|
|
||||||
if (type.IsCanonicalSubtype(CanonicalFormKind.Any))
|
|
||||||
{
|
|
||||||
return new CanonicalEETypeNode(this, type);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return new ConstructedEETypeNode(this, type);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return new ExternEETypeSymbolNode(this, type);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
_clonedTypeSymbols = new NodeCache<TypeDesc, IEETypeNode>((TypeDesc type) =>
|
_clonedTypeSymbols = new NodeCache<TypeDesc, IEETypeNode>((TypeDesc type) =>
|
||||||
{
|
{
|
||||||
|
@ -483,6 +441,57 @@ namespace ILCompiler.DependencyAnalysis
|
||||||
WindowsDebugData = new WindowsDebugDataHelper(this);
|
WindowsDebugData = new WindowsDebugDataHelper(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected virtual IEETypeNode CreateNecessaryTypeNode(TypeDesc type)
|
||||||
|
{
|
||||||
|
Debug.Assert(!_compilationModuleGroup.ShouldReferenceThroughImportTable(type));
|
||||||
|
if (_compilationModuleGroup.ContainsType(type))
|
||||||
|
{
|
||||||
|
if (type.IsGenericDefinition)
|
||||||
|
{
|
||||||
|
return new GenericDefinitionEETypeNode(this, type);
|
||||||
|
}
|
||||||
|
else if (type.IsCanonicalDefinitionType(CanonicalFormKind.Any))
|
||||||
|
{
|
||||||
|
return new CanonicalDefinitionEETypeNode(this, type);
|
||||||
|
}
|
||||||
|
else if (type.IsCanonicalSubtype(CanonicalFormKind.Any))
|
||||||
|
{
|
||||||
|
return new NecessaryCanonicalEETypeNode(this, type);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return new EETypeNode(this, type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return new ExternEETypeSymbolNode(this, type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual IEETypeNode CreateConstructedTypeNode(TypeDesc type)
|
||||||
|
{
|
||||||
|
// Canonical definition types are *not* constructed types (call NecessaryTypeSymbol to get them)
|
||||||
|
Debug.Assert(!type.IsCanonicalDefinitionType(CanonicalFormKind.Any));
|
||||||
|
Debug.Assert(!_compilationModuleGroup.ShouldReferenceThroughImportTable(type));
|
||||||
|
|
||||||
|
if (_compilationModuleGroup.ContainsType(type))
|
||||||
|
{
|
||||||
|
if (type.IsCanonicalSubtype(CanonicalFormKind.Any))
|
||||||
|
{
|
||||||
|
return new CanonicalEETypeNode(this, type);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return new ConstructedEETypeNode(this, type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return new ExternEETypeSymbolNode(this, type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected abstract IMethodNode CreateMethodEntrypointNode(MethodDesc method);
|
protected abstract IMethodNode CreateMethodEntrypointNode(MethodDesc method);
|
||||||
|
|
||||||
protected abstract IMethodNode CreateUnboxingStubNode(MethodDesc method);
|
protected abstract IMethodNode CreateUnboxingStubNode(MethodDesc method);
|
||||||
|
@ -721,7 +730,7 @@ namespace ILCompiler.DependencyAnalysis
|
||||||
return _stringAllocators.GetOrAdd(stringConstructor);
|
return _stringAllocators.GetOrAdd(stringConstructor);
|
||||||
}
|
}
|
||||||
|
|
||||||
private NodeCache<MethodDesc, IMethodNode> _methodEntrypoints;
|
protected NodeCache<MethodDesc, IMethodNode> _methodEntrypoints;
|
||||||
private NodeCache<MethodDesc, IMethodNode> _unboxingStubs;
|
private NodeCache<MethodDesc, IMethodNode> _unboxingStubs;
|
||||||
private NodeCache<IMethodNode, MethodAssociatedDataNode> _methodAssociatedData;
|
private NodeCache<IMethodNode, MethodAssociatedDataNode> _methodAssociatedData;
|
||||||
|
|
||||||
|
|
|
@ -45,8 +45,8 @@ namespace ILCompiler
|
||||||
protected readonly ManifestResourceBlockingPolicy _resourceBlockingPolicy;
|
protected readonly ManifestResourceBlockingPolicy _resourceBlockingPolicy;
|
||||||
|
|
||||||
private List<NonGCStaticsNode> _cctorContextsGenerated = new List<NonGCStaticsNode>();
|
private List<NonGCStaticsNode> _cctorContextsGenerated = new List<NonGCStaticsNode>();
|
||||||
private HashSet<TypeDesc> _typesWithEETypesGenerated = new HashSet<TypeDesc>();
|
private readonly HashSet<TypeDesc> _typesWithEETypesGenerated = new HashSet<TypeDesc>();
|
||||||
private HashSet<TypeDesc> _typesWithConstructedEETypesGenerated = new HashSet<TypeDesc>();
|
private readonly HashSet<TypeDesc> _typesWithConstructedEETypesGenerated = new HashSet<TypeDesc>();
|
||||||
private HashSet<MethodDesc> _methodsGenerated = new HashSet<MethodDesc>();
|
private HashSet<MethodDesc> _methodsGenerated = new HashSet<MethodDesc>();
|
||||||
private HashSet<GenericDictionaryNode> _genericDictionariesGenerated = new HashSet<GenericDictionaryNode>();
|
private HashSet<GenericDictionaryNode> _genericDictionariesGenerated = new HashSet<GenericDictionaryNode>();
|
||||||
private HashSet<IMethodBodyNode> _methodBodiesGenerated = new HashSet<IMethodBodyNode>();
|
private HashSet<IMethodBodyNode> _methodBodiesGenerated = new HashSet<IMethodBodyNode>();
|
||||||
|
@ -560,7 +560,7 @@ namespace ILCompiler
|
||||||
return _genericDictionariesGenerated;
|
return _genericDictionariesGenerated;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal IEnumerable<MethodDesc> GetCompiledMethods()
|
public IEnumerable<MethodDesc> GetCompiledMethods()
|
||||||
{
|
{
|
||||||
return _methodsGenerated;
|
return _methodsGenerated;
|
||||||
}
|
}
|
||||||
|
|
|
@ -72,14 +72,6 @@ namespace ILCompiler.DependencyAnalysis
|
||||||
_rdataSectionIndex = sectionBuilder.AddSection(".rdata", SectionCharacteristics.ContainsInitializedData | SectionCharacteristics.MemRead, 512);
|
_rdataSectionIndex = sectionBuilder.AddSection(".rdata", SectionCharacteristics.ContainsInitializedData | SectionCharacteristics.MemRead, 512);
|
||||||
_dataSectionIndex = sectionBuilder.AddSection(".data", SectionCharacteristics.ContainsInitializedData | SectionCharacteristics.MemWrite | SectionCharacteristics.MemRead, 512);
|
_dataSectionIndex = sectionBuilder.AddSection(".data", SectionCharacteristics.ContainsInitializedData | SectionCharacteristics.MemWrite | SectionCharacteristics.MemRead, 512);
|
||||||
|
|
||||||
foreach (var depNode in _nodes)
|
|
||||||
{
|
|
||||||
if (depNode is EETypeNode eeTypeNode)
|
|
||||||
{
|
|
||||||
_nodeFactory.TypesTable.Add(eeTypeNode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int nodeIndex = -1;
|
int nodeIndex = -1;
|
||||||
foreach (var depNode in _nodes)
|
foreach (var depNode in _nodes)
|
||||||
{
|
{
|
||||||
|
|
|
@ -0,0 +1,62 @@
|
||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
using ILCompiler.DependencyAnalysisFramework;
|
||||||
|
|
||||||
|
using Internal.Text;
|
||||||
|
using Internal.TypeSystem;
|
||||||
|
|
||||||
|
namespace ILCompiler.DependencyAnalysis.ReadyToRun
|
||||||
|
{
|
||||||
|
class ExternalTypeNode : DependencyNodeCore<NodeFactory>, IEETypeNode
|
||||||
|
{
|
||||||
|
private readonly TypeDesc _type;
|
||||||
|
|
||||||
|
public ExternalTypeNode(NodeFactory factory, TypeDesc type)
|
||||||
|
{
|
||||||
|
_type = type;
|
||||||
|
|
||||||
|
//
|
||||||
|
// This check encodes rules specific to CoreRT. Ie, no function pointer classes allowed.
|
||||||
|
// Eventually we will hit situations where this check fails when it shouldn't and we'll need to
|
||||||
|
// split the logic. It's a good sanity check for the time being though.
|
||||||
|
//
|
||||||
|
EETypeNode.CheckCanGenerateEEType(factory, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TypeDesc Type => _type;
|
||||||
|
|
||||||
|
public int Offset => 0;
|
||||||
|
|
||||||
|
public bool RepresentsIndirectionCell => false;
|
||||||
|
|
||||||
|
public int ClassCode => -1044459;
|
||||||
|
|
||||||
|
public override bool InterestingForDynamicDependencyAnalysis => false;
|
||||||
|
|
||||||
|
public override bool HasDynamicDependencies => false;
|
||||||
|
|
||||||
|
public override bool HasConditionalStaticDependencies => false;
|
||||||
|
|
||||||
|
public override bool StaticDependenciesAreComputed => true;
|
||||||
|
|
||||||
|
public void AppendMangledName(NameMangler nameMangler, Utf8StringBuilder sb)
|
||||||
|
{
|
||||||
|
sb.Append(nameMangler.GetMangledTypeName(_type));
|
||||||
|
}
|
||||||
|
|
||||||
|
public int CompareToImpl(ISortableNode other, CompilerComparer comparer)
|
||||||
|
{
|
||||||
|
return comparer.Compare(Type, ((AvailableType)other).Type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IEnumerable<CombinedDependencyListEntry> GetConditionalStaticDependencies(NodeFactory context) => null;
|
||||||
|
public override IEnumerable<CombinedDependencyListEntry> SearchDynamicDependencies(List<DependencyNodeCore<NodeFactory>> markedNodes, int firstNode, NodeFactory context) => null;
|
||||||
|
public override IEnumerable<DependencyListEntry> GetStaticDependencies(NodeFactory context) => null;
|
||||||
|
|
||||||
|
protected override string GetName(NodeFactory factory) => $"Externally referenced type {Type.ToString()}";
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,62 @@
|
||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
using ILCompiler.DependencyAnalysisFramework;
|
||||||
|
|
||||||
|
using Internal.Text;
|
||||||
|
using Internal.TypeSystem;
|
||||||
|
|
||||||
|
namespace ILCompiler.DependencyAnalysis.ReadyToRun
|
||||||
|
{
|
||||||
|
public class AvailableType : DependencyNodeCore<NodeFactory>, IEETypeNode
|
||||||
|
{
|
||||||
|
private readonly TypeDesc _type;
|
||||||
|
|
||||||
|
public AvailableType(NodeFactory factory, TypeDesc type)
|
||||||
|
{
|
||||||
|
_type = type;
|
||||||
|
|
||||||
|
//
|
||||||
|
// This check encodes rules specific to CoreRT. Ie, no function pointer classes allowed.
|
||||||
|
// Eventually we will hit situations where this check fails when it shouldn't and we'll need to
|
||||||
|
// split the logic. It's a good sanity check for the time being though.
|
||||||
|
//
|
||||||
|
EETypeNode.CheckCanGenerateEEType(factory, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TypeDesc Type => _type;
|
||||||
|
|
||||||
|
public int Offset => 0;
|
||||||
|
|
||||||
|
public bool RepresentsIndirectionCell => false;
|
||||||
|
|
||||||
|
public int ClassCode => 345483495;
|
||||||
|
|
||||||
|
public override bool InterestingForDynamicDependencyAnalysis => false;
|
||||||
|
|
||||||
|
public override bool HasDynamicDependencies => false;
|
||||||
|
|
||||||
|
public override bool HasConditionalStaticDependencies => false;
|
||||||
|
|
||||||
|
public override bool StaticDependenciesAreComputed => true;
|
||||||
|
|
||||||
|
public void AppendMangledName(NameMangler nameMangler, Utf8StringBuilder sb)
|
||||||
|
{
|
||||||
|
sb.Append(nameMangler.GetMangledTypeName(_type));
|
||||||
|
}
|
||||||
|
|
||||||
|
public int CompareToImpl(ISortableNode other, CompilerComparer comparer)
|
||||||
|
{
|
||||||
|
return comparer.Compare(Type, ((AvailableType)other).Type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IEnumerable<CombinedDependencyListEntry> GetConditionalStaticDependencies(NodeFactory context) => null;
|
||||||
|
public override IEnumerable<CombinedDependencyListEntry> SearchDynamicDependencies(List<DependencyNodeCore<NodeFactory>> markedNodes, int firstNode, NodeFactory context) => null;
|
||||||
|
public override IEnumerable<DependencyListEntry> GetStaticDependencies(NodeFactory context) => null;
|
||||||
|
|
||||||
|
protected override string GetName(NodeFactory factory) => $"Available type {Type.ToString()}";
|
||||||
|
}
|
||||||
|
}
|
|
@ -32,6 +32,7 @@ namespace ILCompiler.DependencyAnalysis.ReadyToRun
|
||||||
}
|
}
|
||||||
|
|
||||||
public MethodDesc Method => _methodDesc;
|
public MethodDesc Method => _methodDesc;
|
||||||
|
public MethodWithGCInfo MethodCodeNode => _localMethod;
|
||||||
|
|
||||||
public override int ClassCode => 458823351;
|
public override int ClassCode => 458823351;
|
||||||
|
|
||||||
|
|
|
@ -66,7 +66,7 @@ namespace ILCompiler.DependencyAnalysis.ReadyToRun
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
List<EntryPoint> _ridToEntryPoint;
|
private List<EntryPoint> _ridToEntryPoint;
|
||||||
|
|
||||||
public MethodEntryPointTableNode(TargetDetails target)
|
public MethodEntryPointTableNode(TargetDetails target)
|
||||||
: base(target)
|
: base(target)
|
||||||
|
@ -200,6 +200,19 @@ namespace ILCompiler.DependencyAnalysis.ReadyToRun
|
||||||
return new ObjectData(Array.Empty<byte>(), Array.Empty<Relocation>(), 1, Array.Empty<ISymbolDefinitionNode>());
|
return new ObjectData(Array.Empty<byte>(), Array.Empty<Relocation>(), 1, Array.Empty<ISymbolDefinitionNode>());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
foreach (MethodDesc method in factory.MetadataManager.GetCompiledMethods())
|
||||||
|
{
|
||||||
|
MethodWithGCInfo methodCodeNode = factory.MethodEntrypoint(method) as MethodWithGCInfo;
|
||||||
|
if (methodCodeNode == null)
|
||||||
|
{
|
||||||
|
methodCodeNode = ((ExternalMethodImport)factory.MethodEntrypoint(method))?.MethodCodeNode;
|
||||||
|
if (methodCodeNode == null)
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Add(methodCodeNode, ((ReadyToRunCodegenNodeFactory)factory).RuntimeFunctionsTable.GetIndex(methodCodeNode));
|
||||||
|
}
|
||||||
|
|
||||||
NativeWriter writer = new NativeWriter();
|
NativeWriter writer = new NativeWriter();
|
||||||
|
|
||||||
Section arraySection = writer.NewSection();
|
Section arraySection = writer.NewSection();
|
||||||
|
|
|
@ -2,9 +2,10 @@
|
||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
|
||||||
using ILCompiler.DependencyAnalysisFramework;
|
using ILCompiler.DependencyAnalysisFramework;
|
||||||
|
|
||||||
using Internal.Text;
|
using Internal.Text;
|
||||||
using Internal.TypeSystem;
|
using Internal.TypeSystem;
|
||||||
|
|
||||||
|
@ -43,10 +44,7 @@ namespace ILCompiler.DependencyAnalysis.ReadyToRun
|
||||||
protected override void OnMarked(NodeFactory factory)
|
protected override void OnMarked(NodeFactory factory)
|
||||||
{
|
{
|
||||||
ReadyToRunCodegenNodeFactory r2rFactory = (ReadyToRunCodegenNodeFactory)factory;
|
ReadyToRunCodegenNodeFactory r2rFactory = (ReadyToRunCodegenNodeFactory)factory;
|
||||||
// Marked method - add runtime & entry point table entry
|
|
||||||
r2rFactory.RuntimeFunctionsGCInfo.AddEmbeddedObject(GCInfoNode);
|
r2rFactory.RuntimeFunctionsGCInfo.AddEmbeddedObject(GCInfoNode);
|
||||||
int index = r2rFactory.RuntimeFunctionsTable.Add(this);
|
|
||||||
r2rFactory.MethodEntryPointTable.Add(this, index);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override ObjectData GetData(NodeFactory factory, bool relocsOnly)
|
public override ObjectData GetData(NodeFactory factory, bool relocsOnly)
|
||||||
|
|
|
@ -44,5 +44,12 @@ namespace ILCompiler.DependencyAnalysis.ReadyToRun
|
||||||
{
|
{
|
||||||
return _typeToken.CompareTo(((NewArrayFixupSignature)other)._typeToken);
|
return _typeToken.CompareTo(((NewArrayFixupSignature)other)._typeToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override DependencyList ComputeNonRelocationBasedDependencies(NodeFactory factory)
|
||||||
|
{
|
||||||
|
DependencyList dependencies = new DependencyList();
|
||||||
|
dependencies.Add(factory.NecessaryTypeSymbol(_arrayType.ElementType), "Type used as array element");
|
||||||
|
return dependencies;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using ILCompiler.DependencyAnalysisFramework;
|
||||||
using Internal.Text;
|
using Internal.Text;
|
||||||
using Internal.TypeSystem;
|
using Internal.TypeSystem;
|
||||||
|
|
||||||
|
@ -44,5 +45,12 @@ namespace ILCompiler.DependencyAnalysis.ReadyToRun
|
||||||
{
|
{
|
||||||
return _typeToken.CompareTo(((NewObjectFixupSignature)other)._typeToken);
|
return _typeToken.CompareTo(((NewObjectFixupSignature)other)._typeToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override DependencyList ComputeNonRelocationBasedDependencies(NodeFactory factory)
|
||||||
|
{
|
||||||
|
DependencyList dependencies = new DependencyList();
|
||||||
|
dependencies.Add(factory.ConstructedTypeSymbol(_typeDesc), "Type constructed through new object fixup");
|
||||||
|
return dependencies;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,20 +2,26 @@
|
||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
using Internal.Text;
|
using Internal.Text;
|
||||||
using Internal.TypeSystem;
|
using Internal.TypeSystem;
|
||||||
|
|
||||||
|
using Debug = System.Diagnostics.Debug;
|
||||||
|
|
||||||
namespace ILCompiler.DependencyAnalysis.ReadyToRun
|
namespace ILCompiler.DependencyAnalysis.ReadyToRun
|
||||||
{
|
{
|
||||||
public class RuntimeFunctionsTableNode : HeaderTableNode
|
public class RuntimeFunctionsTableNode : HeaderTableNode
|
||||||
{
|
{
|
||||||
private readonly List<MethodWithGCInfo> _methodNodes;
|
private List<MethodWithGCInfo> _methodNodes;
|
||||||
|
private Dictionary<MethodWithGCInfo, int> _insertedMethodNodes;
|
||||||
|
private readonly NodeFactory _nodeFactory;
|
||||||
|
|
||||||
public RuntimeFunctionsTableNode(TargetDetails target)
|
public RuntimeFunctionsTableNode(NodeFactory nodeFactory)
|
||||||
: base(target)
|
: base(nodeFactory.Target)
|
||||||
{
|
{
|
||||||
_methodNodes = new List<MethodWithGCInfo>();
|
_nodeFactory = nodeFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void AppendMangledName(NameMangler nameMangler, Utf8StringBuilder sb)
|
public override void AppendMangledName(NameMangler nameMangler, Utf8StringBuilder sb)
|
||||||
|
@ -24,14 +30,47 @@ namespace ILCompiler.DependencyAnalysis.ReadyToRun
|
||||||
sb.Append("__ReadyToRunRuntimeFunctionsTable");
|
sb.Append("__ReadyToRunRuntimeFunctionsTable");
|
||||||
}
|
}
|
||||||
|
|
||||||
public int Add(MethodWithGCInfo method)
|
public int GetIndex(MethodWithGCInfo method)
|
||||||
{
|
{
|
||||||
_methodNodes.Add(method);
|
#if DEBUG
|
||||||
return _methodNodes.Count - 1;
|
Debug.Assert(_nodeFactory.MarkingComplete);
|
||||||
|
Debug.Assert(method.Marked);
|
||||||
|
#endif
|
||||||
|
if (_methodNodes == null)
|
||||||
|
LayoutRuntimeFunctions();
|
||||||
|
|
||||||
|
return _insertedMethodNodes[method];
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LayoutRuntimeFunctions()
|
||||||
|
{
|
||||||
|
_methodNodes = new List<MethodWithGCInfo>();
|
||||||
|
_insertedMethodNodes = new Dictionary<MethodWithGCInfo, int>();
|
||||||
|
|
||||||
|
foreach (MethodDesc method in _nodeFactory.MetadataManager.GetCompiledMethods())
|
||||||
|
{
|
||||||
|
MethodWithGCInfo methodCodeNode = _nodeFactory.MethodEntrypoint(method) as MethodWithGCInfo;
|
||||||
|
if (methodCodeNode == null)
|
||||||
|
{
|
||||||
|
methodCodeNode = ((ExternalMethodImport)_nodeFactory.MethodEntrypoint(method))?.MethodCodeNode;
|
||||||
|
if (methodCodeNode == null)
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
_methodNodes.Add(methodCodeNode);
|
||||||
|
_insertedMethodNodes[methodCodeNode] = _methodNodes.Count - 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false)
|
public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false)
|
||||||
{
|
{
|
||||||
|
// This node does not trigger generation of other nodes.
|
||||||
|
if (relocsOnly)
|
||||||
|
return new ObjectData(Array.Empty<byte>(), Array.Empty<Relocation>(), 1, new ISymbolDefinitionNode[] { this });
|
||||||
|
|
||||||
|
if (_methodNodes == null)
|
||||||
|
LayoutRuntimeFunctions();
|
||||||
|
|
||||||
ObjectDataBuilder runtimeFunctionsBuilder = new ObjectDataBuilder(factory, relocsOnly);
|
ObjectDataBuilder runtimeFunctionsBuilder = new ObjectDataBuilder(factory, relocsOnly);
|
||||||
|
|
||||||
// Add the symbol representing this object node
|
// Add the symbol representing this object node
|
||||||
|
|
|
@ -50,5 +50,12 @@ namespace ILCompiler.DependencyAnalysis.ReadyToRun
|
||||||
{
|
{
|
||||||
return _typeToken.CompareTo(((TypeFixupSignature)other)._typeToken);
|
return _typeToken.CompareTo(((TypeFixupSignature)other)._typeToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override DependencyList ComputeNonRelocationBasedDependencies(NodeFactory factory)
|
||||||
|
{
|
||||||
|
DependencyList dependencies = new DependencyList();
|
||||||
|
dependencies.Add(factory.NecessaryTypeSymbol(_typeDesc), "Type referenced in a fixup signature");
|
||||||
|
return dependencies;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,13 +17,8 @@ namespace ILCompiler.DependencyAnalysis.ReadyToRun
|
||||||
{
|
{
|
||||||
public class TypesTableNode : HeaderTableNode
|
public class TypesTableNode : HeaderTableNode
|
||||||
{
|
{
|
||||||
List<(int Rid, EETypeNode Node)> _eeTypeNodes;
|
|
||||||
|
|
||||||
public TypesTableNode(TargetDetails target)
|
public TypesTableNode(TargetDetails target)
|
||||||
: base(target)
|
: base(target) {}
|
||||||
{
|
|
||||||
_eeTypeNodes = new List<(int Rid, EETypeNode Node)>();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void AppendMangledName(NameMangler nameMangler, Utf8StringBuilder sb)
|
public override void AppendMangledName(NameMangler nameMangler, Utf8StringBuilder sb)
|
||||||
{
|
{
|
||||||
|
@ -31,34 +26,33 @@ namespace ILCompiler.DependencyAnalysis.ReadyToRun
|
||||||
sb.Append("__ReadyToRunAvailableTypesTable");
|
sb.Append("__ReadyToRunAvailableTypesTable");
|
||||||
}
|
}
|
||||||
|
|
||||||
public int Add(EETypeNode eeTypeNode)
|
|
||||||
{
|
|
||||||
if (eeTypeNode.Type is EcmaType ecmaType)
|
|
||||||
{
|
|
||||||
int rid = MetadataTokens.GetToken(ecmaType.Handle) & 0x00FFFFFF;
|
|
||||||
Debug.Assert(rid != 0);
|
|
||||||
int eeTypeIndex = _eeTypeNodes.Count;
|
|
||||||
_eeTypeNodes.Add((Rid: rid, Node: eeTypeNode));
|
|
||||||
return eeTypeIndex;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false)
|
public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false)
|
||||||
{
|
{
|
||||||
|
// This node does not trigger generation of other nodes.
|
||||||
|
if (relocsOnly)
|
||||||
|
return new ObjectData(Array.Empty<byte>(), Array.Empty<Relocation>(), 1, new ISymbolDefinitionNode[] { this });
|
||||||
|
|
||||||
NativeWriter writer = new NativeWriter();
|
NativeWriter writer = new NativeWriter();
|
||||||
Section section = writer.NewSection();
|
Section section = writer.NewSection();
|
||||||
|
|
||||||
VertexHashtable typesHashtable = new VertexHashtable();
|
VertexHashtable typesHashtable = new VertexHashtable();
|
||||||
section.Place(typesHashtable);
|
section.Place(typesHashtable);
|
||||||
|
|
||||||
foreach ((int Rid, EETypeNode Node) eeTypeNode in _eeTypeNodes)
|
foreach (TypeDesc type in ((ReadyToRunTableManager)factory.MetadataManager).GetTypesWithAvailableTypes())
|
||||||
{
|
{
|
||||||
int hashCode = eeTypeNode.Node.Type.GetHashCode();
|
int rid = 0;
|
||||||
typesHashtable.Append(unchecked((uint)hashCode), section.Place(new UnsignedConstant((uint)eeTypeNode.Rid << 1)));
|
if (type is EcmaType ecmaType)
|
||||||
|
{
|
||||||
|
rid = MetadataTokens.GetToken(ecmaType.Handle) & 0x00FFFFFF;
|
||||||
|
Debug.Assert(rid != 0);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
int hashCode = type.GetHashCode();
|
||||||
|
typesHashtable.Append(unchecked((uint)hashCode), section.Place(new UnsignedConstant((uint)rid << 1)));
|
||||||
}
|
}
|
||||||
|
|
||||||
MemoryStream writerContent = new MemoryStream();
|
MemoryStream writerContent = new MemoryStream();
|
||||||
|
|
|
@ -74,17 +74,12 @@ namespace ILCompiler.DependencyAnalysis
|
||||||
|
|
||||||
public ImportSectionNode PrecodeImports;
|
public ImportSectionNode PrecodeImports;
|
||||||
|
|
||||||
Dictionary<MethodDesc, IMethodNode> _methodMap = new Dictionary<MethodDesc, IMethodNode>();
|
|
||||||
|
|
||||||
public IMethodNode MethodEntrypoint(MethodDesc method, ModuleToken token, bool isUnboxingStub = false)
|
public IMethodNode MethodEntrypoint(MethodDesc method, ModuleToken token, bool isUnboxingStub = false)
|
||||||
{
|
{
|
||||||
IMethodNode methodNode;
|
return _methodEntrypoints.GetOrAdd(method, (m) =>
|
||||||
if (!_methodMap.TryGetValue(method, out methodNode))
|
|
||||||
{
|
{
|
||||||
methodNode = CreateMethodEntrypointNode(method, token, isUnboxingStub);
|
return CreateMethodEntrypointNode(method, token, isUnboxingStub);
|
||||||
_methodMap.Add(method, methodNode);
|
});
|
||||||
}
|
|
||||||
return methodNode;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private IMethodNode CreateMethodEntrypointNode(MethodDesc method, ModuleToken token, bool isUnboxingStub = false)
|
private IMethodNode CreateMethodEntrypointNode(MethodDesc method, ModuleToken token, bool isUnboxingStub = false)
|
||||||
|
@ -589,7 +584,7 @@ namespace ILCompiler.DependencyAnalysis
|
||||||
var compilerIdentifierNode = new CompilerIdentifierNode(Target);
|
var compilerIdentifierNode = new CompilerIdentifierNode(Target);
|
||||||
Header.Add(Internal.Runtime.ReadyToRunSectionType.CompilerIdentifier, compilerIdentifierNode, compilerIdentifierNode);
|
Header.Add(Internal.Runtime.ReadyToRunSectionType.CompilerIdentifier, compilerIdentifierNode, compilerIdentifierNode);
|
||||||
|
|
||||||
RuntimeFunctionsTable = new RuntimeFunctionsTableNode(Target);
|
RuntimeFunctionsTable = new RuntimeFunctionsTableNode(this);
|
||||||
Header.Add(Internal.Runtime.ReadyToRunSectionType.RuntimeFunctions, RuntimeFunctionsTable, RuntimeFunctionsTable);
|
Header.Add(Internal.Runtime.ReadyToRunSectionType.RuntimeFunctions, RuntimeFunctionsTable, RuntimeFunctionsTable);
|
||||||
|
|
||||||
RuntimeFunctionsGCInfo = new RuntimeFunctionsGCInfoNode();
|
RuntimeFunctionsGCInfo = new RuntimeFunctionsGCInfoNode();
|
||||||
|
@ -660,6 +655,8 @@ namespace ILCompiler.DependencyAnalysis
|
||||||
graph.AddRoot(PrecodeImports, "Precode imports are always generated");
|
graph.AddRoot(PrecodeImports, "Precode imports are always generated");
|
||||||
graph.AddRoot(StringImports, "String imports are always generated");
|
graph.AddRoot(StringImports, "String imports are always generated");
|
||||||
graph.AddRoot(Header, "ReadyToRunHeader is always generated");
|
graph.AddRoot(Header, "ReadyToRunHeader is always generated");
|
||||||
|
|
||||||
|
MetadataManager.AttachToDependencyGraph(graph);
|
||||||
}
|
}
|
||||||
|
|
||||||
public IMethodNode ImportedMethodNode(MethodDesc method, bool unboxingStub, ModuleToken token, MethodWithGCInfo localMethod)
|
public IMethodNode ImportedMethodNode(MethodDesc method, bool unboxingStub, ModuleToken token, MethodWithGCInfo localMethod)
|
||||||
|
@ -717,6 +714,33 @@ namespace ILCompiler.DependencyAnalysis
|
||||||
return MethodEntrypoint(method, token, isUnboxingStub);
|
return MethodEntrypoint(method, token, isUnboxingStub);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override IEETypeNode CreateNecessaryTypeNode(TypeDesc type)
|
||||||
|
{
|
||||||
|
if (CompilationModuleGroup.ContainsType(type))
|
||||||
|
{
|
||||||
|
return new AvailableType(this, type);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return new ExternalTypeNode(this, type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override IEETypeNode CreateConstructedTypeNode(TypeDesc type)
|
||||||
|
{
|
||||||
|
// Canonical definition types are *not* constructed types (call NecessaryTypeSymbol to get them)
|
||||||
|
Debug.Assert(!type.IsCanonicalDefinitionType(CanonicalFormKind.Any));
|
||||||
|
|
||||||
|
if (CompilationModuleGroup.ContainsType(type))
|
||||||
|
{
|
||||||
|
return new AvailableType(this, type);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return new ExternalTypeNode(this, type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected override IMethodNode CreateMethodEntrypointNode(MethodDesc method)
|
protected override IMethodNode CreateMethodEntrypointNode(MethodDesc method)
|
||||||
{
|
{
|
||||||
if (!CompilationModuleGroup.ContainsMethodBody(method, unboxingStub: false))
|
if (!CompilationModuleGroup.ContainsMethodBody(method, unboxingStub: false))
|
||||||
|
|
|
@ -0,0 +1,57 @@
|
||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
using ILCompiler.DependencyAnalysis;
|
||||||
|
using ILCompiler.DependencyAnalysis.ReadyToRun;
|
||||||
|
using ILCompiler.DependencyAnalysisFramework;
|
||||||
|
|
||||||
|
using Internal.TypeSystem;
|
||||||
|
|
||||||
|
using Debug = System.Diagnostics.Debug;
|
||||||
|
|
||||||
|
namespace ILCompiler
|
||||||
|
{
|
||||||
|
public class ReadyToRunTableManager : MetadataManager
|
||||||
|
{
|
||||||
|
private readonly HashSet<TypeDesc> _typesWithAvailableTypesGenerated = new HashSet<TypeDesc>();
|
||||||
|
|
||||||
|
public ReadyToRunTableManager(CompilerTypeSystemContext typeSystemContext)
|
||||||
|
: base(typeSystemContext, new NoMetadataBlockingPolicy(), new NoManifestResourceBlockingPolicy()) {}
|
||||||
|
|
||||||
|
public override void AddToReadyToRunHeader(ReadyToRunHeaderNode header, NodeFactory nodeFactory, ExternalReferencesTableNode commonFixupsTableNode)
|
||||||
|
{
|
||||||
|
// We don't attach any metadata blobs.
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Graph_NewMarkedNode(DependencyNodeCore<NodeFactory> obj)
|
||||||
|
{
|
||||||
|
base.Graph_NewMarkedNode(obj);
|
||||||
|
|
||||||
|
var eetypeNode = obj as AvailableType;
|
||||||
|
if (eetypeNode != null)
|
||||||
|
{
|
||||||
|
_typesWithAvailableTypesGenerated.Add(eetypeNode.Type);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<TypeDesc> GetTypesWithAvailableTypes()
|
||||||
|
{
|
||||||
|
return _typesWithAvailableTypesGenerated;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override MethodDesc GetCanonicalReflectionInvokeStub(MethodDesc method) => throw new NotImplementedException();
|
||||||
|
public override IEnumerable<ModuleDesc> GetCompilationModulesWithMetadata() => throw new NotImplementedException();
|
||||||
|
public override bool HasReflectionInvokeStubForInvokableMethod(MethodDesc method) => throw new NotImplementedException();
|
||||||
|
public override bool WillUseMetadataTokenToReferenceField(FieldDesc field) => throw new NotImplementedException();
|
||||||
|
public override bool WillUseMetadataTokenToReferenceMethod(MethodDesc method) => throw new NotImplementedException();
|
||||||
|
protected override void ComputeMetadata(NodeFactory factory, out byte[] metadataBlob, out List<MetadataMapping<MetadataType>> typeMappings, out List<MetadataMapping<MethodDesc>> methodMappings, out List<MetadataMapping<FieldDesc>> fieldMappings, out List<MetadataMapping<MethodDesc>> stackTraceMapping) => throw new NotImplementedException();
|
||||||
|
protected override MetadataCategory GetMetadataCategory(MethodDesc method) => throw new NotImplementedException();
|
||||||
|
protected override MetadataCategory GetMetadataCategory(TypeDesc type) => throw new NotImplementedException();
|
||||||
|
protected override MetadataCategory GetMetadataCategory(FieldDesc field) => throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
|
@ -28,7 +28,9 @@
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="CodeGen\ReadyToRunObjectWriter.cs" />
|
<Compile Include="CodeGen\ReadyToRunObjectWriter.cs" />
|
||||||
|
<Compile Include="Compiler\DependencyAnalysis\ExternalTypeNode.cs" />
|
||||||
<Compile Include="Compiler\DependencyAnalysis\ReadyToRunCodegenNodeFactory.cs" />
|
<Compile Include="Compiler\DependencyAnalysis\ReadyToRunCodegenNodeFactory.cs" />
|
||||||
|
<Compile Include="Compiler\DependencyAnalysis\ReadyToRun\AvailableType.cs" />
|
||||||
<Compile Include="Compiler\DependencyAnalysis\ReadyToRun\ByteArrayComparer.cs" />
|
<Compile Include="Compiler\DependencyAnalysis\ReadyToRun\ByteArrayComparer.cs" />
|
||||||
<Compile Include="Compiler\DependencyAnalysis\ReadyToRun\CompilerIdentifierNode.cs" />
|
<Compile Include="Compiler\DependencyAnalysis\ReadyToRun\CompilerIdentifierNode.cs" />
|
||||||
<Compile Include="Compiler\DependencyAnalysis\ReadyToRun\DelayLoadHelperImport.cs" />
|
<Compile Include="Compiler\DependencyAnalysis\ReadyToRun\DelayLoadHelperImport.cs" />
|
||||||
|
@ -72,6 +74,7 @@
|
||||||
<Compile Include="Compiler\ReadyToRunMetadataFieldLayoutAlgorithm.cs" />
|
<Compile Include="Compiler\ReadyToRunMetadataFieldLayoutAlgorithm.cs" />
|
||||||
<Compile Include="Compiler\ReadyToRunNodeMangler.cs" />
|
<Compile Include="Compiler\ReadyToRunNodeMangler.cs" />
|
||||||
<Compile Include="Compiler\ReadyToRunSingleAssemblyCompilationModuleGroup.cs" />
|
<Compile Include="Compiler\ReadyToRunSingleAssemblyCompilationModuleGroup.cs" />
|
||||||
|
<Compile Include="Compiler\ReadyToRunTableManager.cs" />
|
||||||
<Compile Include="JitInterface\CorInfoImpl.ReadyToRun.cs" />
|
<Compile Include="JitInterface\CorInfoImpl.ReadyToRun.cs" />
|
||||||
<Compile Include="ObjectWriter\SectionBuilder.cs" />
|
<Compile Include="ObjectWriter\SectionBuilder.cs" />
|
||||||
<Compile Include="ObjectWriter\R2RPEBuilder.cs" />
|
<Compile Include="ObjectWriter\R2RPEBuilder.cs" />
|
||||||
|
|
|
@ -491,7 +491,16 @@ namespace ILCompiler
|
||||||
|
|
||||||
bool supportsReflection = !_isReadyToRunCodeGen && !_isWasmCodegen && !_isCppCodegen && _systemModuleName == DefaultSystemModule;
|
bool supportsReflection = !_isReadyToRunCodeGen && !_isWasmCodegen && !_isCppCodegen && _systemModuleName == DefaultSystemModule;
|
||||||
|
|
||||||
MetadataManager compilationMetadataManager = supportsReflection ? metadataManager : (MetadataManager)new EmptyMetadataManager(typeSystemContext);
|
MetadataManager compilationMetadataManager;
|
||||||
|
if (_isReadyToRunCodeGen)
|
||||||
|
{
|
||||||
|
compilationMetadataManager = new ReadyToRunTableManager(typeSystemContext);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
compilationMetadataManager = supportsReflection ? metadataManager : (MetadataManager)new EmptyMetadataManager(typeSystemContext);
|
||||||
|
}
|
||||||
|
|
||||||
ILScanResults scanResults = null;
|
ILScanResults scanResults = null;
|
||||||
if (useScanner)
|
if (useScanner)
|
||||||
{
|
{
|
||||||
|
|
|
@ -261,6 +261,25 @@ internal class Program
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool CreateLocalClassInstance()
|
||||||
|
{
|
||||||
|
var testClass = new TestClass(1234);
|
||||||
|
Console.WriteLine("Successfully constructed TestClass");
|
||||||
|
return testClass.A == 1234;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestClass
|
||||||
|
{
|
||||||
|
private int _a;
|
||||||
|
|
||||||
|
public TestClass(int a)
|
||||||
|
{
|
||||||
|
_a = a;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int A => _a;
|
||||||
|
}
|
||||||
|
|
||||||
public static int Main(string[] args)
|
public static int Main(string[] args)
|
||||||
{
|
{
|
||||||
if (args.Length > 0)
|
if (args.Length > 0)
|
||||||
|
@ -290,6 +309,8 @@ internal class Program
|
||||||
RunTest("ManipulateListOfString", ManipulateListOfString());
|
RunTest("ManipulateListOfString", ManipulateListOfString());
|
||||||
|
|
||||||
RunTest("EmptyArray", EmptyArray());
|
RunTest("EmptyArray", EmptyArray());
|
||||||
|
RunTest("CreateLocalClassInstance", CreateLocalClassInstance());
|
||||||
|
|
||||||
// TODO: RunTest("EnumerateEmptyArray", EnumerateEmptyArray());
|
// TODO: RunTest("EnumerateEmptyArray", EnumerateEmptyArray());
|
||||||
|
|
||||||
Console.WriteLine($@"{_passedTests.Count} tests pass:");
|
Console.WriteLine($@"{_passedTests.Count} tests pass:");
|
||||||
|
|
Загрузка…
Ссылка в новой задаче