Initial support for preinitialized array in CoreRT#3558
Initial support for preinitialized array in CoreRT#3558yizhang82 merged 12 commits intodotnet:masterfrom
Conversation
| StringTable = 200, // Unused | ||
| GCStaticRegion = 201, | ||
| ThreadStaticRegion = 202, | ||
| GCStaticsRegion = 201, |
There was a problem hiding this comment.
These renames are unnecessary and expand the already big diff to include unrelated files. This slows down the code reviews, but maybe it's just me.
| // Skip SyncBlock + EEType when calculating field size | ||
| int fieldSize = (int)obj.EETypePtr.BaseSize - IntPtr.Size * 2; | ||
|
|
||
| RuntimeAugments.BulkMoveWithWriteBarrier( |
There was a problem hiding this comment.
This should use RuntimeImports directly. RuntimeAugments is for use outside of CoreLib. This will also fix the build break.
There was a problem hiding this comment.
Seems like RhBulkMoveWithWriteBarrier also doesn't require the buffers to be pinned (it takes ref parameters), so the line below can be changed from new IntPtr((byte*)pEEType + IntPtr.Size) to ref obj.GetRawData()
| object obj = RuntimeImports.RhNewObject(new EETypePtr(new IntPtr((*pBlock).ToInt64() & ~0x1L))); | ||
| object obj = RuntimeImports.RhNewObject(new EETypePtr(new IntPtr(blockAddr & ~0x3L))); | ||
|
|
||
| if ((blockAddr & 0x2L) == 2) |
There was a problem hiding this comment.
This is starting to be way too many bits and masking. Can you add the constants and masks to Common\src\Internal\Runtime\RuntimeConstants.cs? That file is included both in the class library and in the compiler.
There was a problem hiding this comment.
Sure. Added GCStaticRegionConstants
| /// whose RVA points to the contents of the value (such as array). | ||
| /// Returns the corresponding field that contains preinitialized data. | ||
| /// </summary> | ||
| public virtual FieldDesc PreInitDataField => null; |
There was a problem hiding this comment.
We haven't been putting things that don't exist in the ECMA-335 spec into the core type system.
Getting at the preinitialized data should ideally be a service provided by something else, not by the field itself.
All the code that assumes a field knows how to provide it's preinitialized data will need to be rewritten as soon as we start writing our own StaticInitDataTransform equivalent in the compiler. I would prefer we have a centralized place that provides preinitialized data blobs for a given type right away instead of having to rewrite all of this. The mechanism by which the preinitialized data is calculated (be it custom attributes, or interpreting the cctors) should be an implementation detail of that component and shouldn't leak into many components.
There was a problem hiding this comment.
Great point. I'm also a bit unsure about whether I should put the preinitialized information (and learning things as I go) so putting on FieldDesc seems to be the most natural choice at that moment :) But you are right and I agree - moving this into its own helper class seems to be a better choice, and abstracting out the details of where the preinitialized data are is also very nice . I was mostly worry about caching the result but it looks like a natural place to avoid the duplicate calculation is in EETypeNode. I'll make the change
| case WellKnownType.Array: | ||
| case WellKnownType.MulticastDelegate: | ||
| case WellKnownType.Exception: | ||
| case WellKnownType.Type: |
There was a problem hiding this comment.
The well known types mechanism is used to access types that the type system itself needs to be able to get at in a quick fashion. I don't think this qualifies. You can just rewrite:
if (decodedValue.FixedArguments[0].Type != field.Context.GetWellKnownType(WellKnownType.Type)) into
if (!(decodedValue.FixedArguments[0].Value is TypeDesc))Or use the check we use elsewhere.
There was a problem hiding this comment.
OK. Changed to use TypeDesc instead.
| _type = type; | ||
|
|
||
| // Go through and find all fields with preinitialized data | ||
| foreach (var field in _type.GetFields()) |
There was a problem hiding this comment.
Why can't GCStaticsPreInitDataNode go over the field list and sort it? It's rather odd to do sorting here even though it's a different class that actually needs the sorted field list...
There was a problem hiding this comment.
agreed. Forgot to clean this up. Moved to GCStaticsPreInitDataNode
|
|
||
| protected override DependencyList ComputeNonRelocationBasedDependencies(NodeFactory factory) | ||
| { | ||
| // relocs has all the dependencies we need |
There was a problem hiding this comment.
Then don't override the method :).
|
|
||
| return GetDataForPreInitDataField( | ||
| this, _type, _sortedPreInitFields, | ||
| factory.Target.LayoutPointerSize.AsInt, // CoreRT static size calculation includes EEType - skip it |
There was a problem hiding this comment.
factory.Target.PointerSize
| { | ||
| ObjectDataBuilder builder = new ObjectDataBuilder(factory, relocsOnly); | ||
|
|
||
| builder.RequireInitialAlignment(_type.GCStaticFieldAlignment.AsInt); |
There was a problem hiding this comment.
Should this be just 1? We are going to bitblt the contents of this into a GC-allocated memory region anyway. Aligning to anything else seems like a waste of space.
There was a problem hiding this comment.
The gc safe memory copying routine is permitted to rely on aligned buffers to determine if it needs to operate in a GC safe manner. Its best to keep this aligned.
|
|
||
| int staticOffset = startOffset; | ||
| int staticOffsetEnd = _type.GCStaticFieldSize.AsInt; | ||
| int idx = 0; |
There was a problem hiding this comment.
This will always be zero. Should we increment it sometimes?
There was a problem hiding this comment.
Oops. Forgot to increase. Fixed.
| fixed (IntPtr* pEEType = &obj.m_pEEType) | ||
| { | ||
| // Skip SyncBlock + EEType when calculating field size | ||
| int fieldSize = (int)obj.EETypePtr.BaseSize - IntPtr.Size * 2; |
There was a problem hiding this comment.
(int)obj.EETypePtr.BaseSize - (sizeof(ObjHeader) + sizeof(EEType*)) would be more declarative.
I'm wondering if this should be an API on EETypePtr.
There was a problem hiding this comment.
I'm wondering if this should be an API on EETypePtr.
On a second thought, we might want to make this an internal method on System.Object itself. This complements the existing GetRawData method on System.Object: it gives you the size of the raw data. I would call this GetRawDataSize(). That way we avoid hardcoding assumptions about System.Object's layout in here. which just feels dirty.
There was a problem hiding this comment.
I've added both on Object and EEType.
davidwrighton
left a comment
There was a problem hiding this comment.
Needs a bit more work. Most important to me is to leave the api surface for encoding data such that the backend compiler is able to provide pre-initialized state.
| { | ||
| ObjectDataBuilder builder = new ObjectDataBuilder(factory, relocsOnly); | ||
|
|
||
| builder.RequireInitialAlignment(_type.GCStaticFieldAlignment.AsInt); |
There was a problem hiding this comment.
The gc safe memory copying routine is permitted to rely on aligned buffers to determine if it needs to operate in a GC safe manner. Its best to keep this aligned.
src/ILCompiler/repro/Program.cs
Outdated
| // See the LICENSE file in the project root for more information. | ||
|
|
||
| using System; | ||
| using System.Runtime.InteropServices; |
There was a problem hiding this comment.
Please don't check in changes to the standard repro program.
There was a problem hiding this comment.
This is only for my convenience. Will remove before check-in.
src/ILCompiler/repro/repro.il
Outdated
| @@ -0,0 +1,218 @@ | |||
|
|
|||
| // Microsoft (R) .NET Framework IL Disassembler. Version 4.6.1055.0 | |||
There was a problem hiding this comment.
Please don't check in a new repro il file.
| throw new NotSupportedException(); | ||
| } | ||
|
|
||
| FieldDesc arrayDataField = _arrayField.PreInitDataField; |
There was a problem hiding this comment.
Encoding of data should be flexible to allow the backend compiler to compute preinitialized data as well as accepting input from the IL dll that describes the preinitialized data.
|
|
||
| /// <summary> | ||
| /// Contains pointer relocs to preinitialized data in FrozenObjectRegion | ||
| /// </summary> |
There was a problem hiding this comment.
Nit: comment can be omitted. The enum name seems descriptive enough
| [RuntimeImport(RuntimeLibrary, "RhBulkMoveWithWriteBarrier")] | ||
| internal static extern unsafe void RhBulkMoveWithWriteBarrier(ref byte dmem, ref byte smem, nuint size); | ||
|
|
||
| internal static unsafe void RhBulkMoveWithWriteBarrier(IntPtr dmem, IntPtr smem, int size) |
There was a problem hiding this comment.
We prefer to use pointers when dealing with pointers, not IntPtr.
| // which are pointer relocs to GC objects in frozen segment. | ||
| // It actually has all GC fields including non-preinitialized fields and we simply copy over the | ||
| // entire blob to this object, overwriting everything. | ||
| fixed (void* pEEType = &obj.m_pEEType) |
There was a problem hiding this comment.
Why do you need the pinning here?
The existing import of RhBulkMoveWithWriteBarrier that takes refs will work fine with the interor pointer returned by calling obj.GetRawData(). It will also avoid another hardcoding of Object's layout (in the + IntPtr.Size part down below). It looks like a net win not to pin and avoid adding the new API to RuntimeImports...
| { | ||
| get | ||
| { | ||
| return BaseSize - (UInt32) sizeof(ObjHeader) - (UInt32) sizeof(EEType*); |
There was a problem hiding this comment.
As I wrote in my previous comment, I would prefer this to be implemented in System.Object. The sizeof(EEType*) is an implementation detail of System.Object that is hardcoding that System.Object has one pointer-sized field.
There was a problem hiding this comment.
Oh, I see your pointer now. That makes sense. Fixed.
| /// </summary> | ||
| public const int HasPreInitializedData = 0x2; | ||
|
|
||
| public const int All = Initialized + HasPreInitializedData; |
There was a problem hiding this comment.
Nit: call it Mask or something like that?
|
|
||
| TypedReference, | ||
| ByReferenceOfT, | ||
| ByReferenceOfT |
| { | ||
| _type = type; | ||
| _handle = handle; | ||
|
|
There was a problem hiding this comment.
Changes to this file don't seem necessary anymore and leave EcmaField APIs inconsistent with EcmaMethod and EcmaType. Revert?
| private IEETypeNode GetEETypeNode(NodeFactory factory) | ||
| { | ||
| var fieldType = _preInitFieldInfo.Field.FieldType; | ||
| Debug.Assert(factory.IsLocalTypeSymbol(fieldType)); |
There was a problem hiding this comment.
Please delete the IsLocalTypeSymbol helper and inline it here. It's not doing anything sophisticated and results in an extra hashtable lookup to find the EEType (which you get on the line just below this).
There was a problem hiding this comment.
Makes sense. Changed to assert on !RepresentsIndirectionCell.
| if (factory.Target.Abi == TargetAbi.CoreRT) | ||
| { | ||
| builder.EmitPointerReloc(GetGCStaticEETypeNode(factory), 1); | ||
| int delta = 1; |
There was a problem hiding this comment.
- Add
using GCStaticRegionConstants = Internal.Runtime.GCStaticRegionConstantsin the using blocks above and make thisGCStaticRegionConstants.Initialized - Find out that the name of the constant is misleading.
(Can you use the named constant here and rename it to Uninitialized? Same for the 3 below - just | it with the other constant.)
There was a problem hiding this comment.
Oh, yeah, that was backwards. Changed to Uninitialized and switched to use GCStaticRegionConstants.
There was a problem hiding this comment.
Didn't realize we could use the runtime constants in the compiler. Looks like we just include it. that's pretty neat.
There was a problem hiding this comment.
Didn't realize we could use the runtime constants in the compiler. Looks like we just include it. that's pretty neat.
Yup, I started pushing for it because it makes it way easier to connect the places in the classlib that use something with the place in the compiler that emits it.
| return nameMangler.NodeMangler.GCStatics(type) + "__PreInitData"; | ||
| } | ||
|
|
||
| public virtual bool IsExported(NodeFactory factory) => factory.CompilationModuleGroup.ExportsType(Type); |
There was a problem hiding this comment.
Why do we need to export this?
There was a problem hiding this comment.
Not really. This is a copy/paste mistake.
|
|
||
| public override bool StaticDependenciesAreComputed => true; | ||
|
|
||
| public override ObjectNodeSection Section => ObjectNodeSection.DataSection; |
There was a problem hiding this comment.
Should this be in the read only data section on Windows?
There was a problem hiding this comment.
Yes. Changed to ReadOnlyDataSection.
| public class GCStaticsPreInitDataNode : ObjectNode, IExportableSymbolNode | ||
| { | ||
| private MetadataType _type; | ||
| private List<PreInitFieldInfo> _sortedPreInitFields; |
There was a problem hiding this comment.
It might be better to wrap this in a class that can write itself into an ObjectDataBuilder you pass to it instead of passing around a MetadataType and a list of PreInitFieldInfos.
You'll find that handy when you port this to Project X because we won't be allocating this node there, but you'll want most of what you put in this node's logic (the sorting, etc.).
There was a problem hiding this comment.
A scheme like this:
class TypePreinitializationInfo
{
public bool HasInitializedGCStaticData { get; }
public void WriteGCStaticData(ref ObjectDataBuilder builder, NodeFactory factory)
{
// Foreach field, call Write(). Insert padding where needed.
}
// Future: public void WriteNonGCStaticData(ref ObjectDataBuilder builder, NodeFactory factory)
}
abstract class FieldPreinitializationInfo
{
public FieldDesc Field { get; }
public abstract void Write(ref ObjectDataBuilder builder, NodeFactory factory);
}
class ArrayFieldPreinitializationInfo : FieldPreinitializationInfo
{
public override void Write(ref ObjectDataBuilder builder, NodeFactory factory)
{
builder.EmitPointerReloc(factory.SerializedFrozenArray(this));
}
}These can be allocated in a separate class (PreinitializationManager?) that is accessible from NodeFactory.
This will put us into a perfect place for introducing a StaticInitDataTransform later.
There was a problem hiding this comment.
Sounds reasonable but this change is getting pretty big as it is. I'll address this in my next change.
|
@MichalStrehovsky @davidwrighton I believe I have addessed all feedback. PTAL. I'm planning to add IL test and a IL repro project in my next change (acccording to @MichalStrehovsky this might require CLI change so might not be possible yet). |
MichalStrehovsky
left a comment
There was a problem hiding this comment.
Looks good, but there are a few opportunities to delete unused code that shouldn't be missed.
| { | ||
| return !MetadataReader.GetCustomAttributeHandle(MetadataReader.GetFieldDefinition(_handle).GetCustomAttributes(), | ||
| attributeNamespace, attributeName).IsNil; | ||
| return !MetadataReader.GetCustomAttributeHandle(CustomAttributes, attributeNamespace, attributeName).IsNil; |
There was a problem hiding this comment.
Could you revert changes to this file?
| // | ||
| IEETypeNode stringSymbol = factory.ConstructedTypeSymbol(systemStringType); | ||
|
|
||
| if (stringSymbol.RepresentsIndirectionCell) |
There was a problem hiding this comment.
Could you revert these changes too?
| // See the LICENSE file in the project root for more information. | ||
|
|
||
| using System.Collections.Generic; | ||
| using System.Linq; |
There was a problem hiding this comment.
Do you use Linq anywhere here? We generally avoid it in the compiler because it tends to bring generic virtual methods into the picture that nobody likes.
There was a problem hiding this comment.
This is dead code. Removed
| return nameMangler.NodeMangler.GCStatics(type) + "__PreInitData"; | ||
| } | ||
|
|
||
| public virtual bool IsExported(NodeFactory factory) => false; |
There was a problem hiding this comment.
If this always returns false, this shouldn't implement the IExportableSymbolNode interface.
| /// Get a direct reference to this EEType (for example, as required by GC). | ||
| /// If this will be compiled into a separate binary, it must be cloned into this one | ||
| /// </summary> | ||
| public IEETypeNode GetLocalTypeSymbol(TypeDesc type) |
There was a problem hiding this comment.
Could you revert the LocalTypeSymbol part?
| /// <summary> | ||
| /// Contains pointer relocs to preinitialized data in FrozenObjectRegion | ||
| /// </summary> | ||
| GCStaticsPreInitDataRegion = 211, |
There was a problem hiding this comment.
Is this used for anything? It seems like you allocate an ArrayOfEmbeddedObjects for this, but nothing gets actually emitted in the section.
There was a problem hiding this comment.
Not used. Removed
| { | ||
| public class PreInitFieldInfo | ||
| { | ||
| public FieldDesc Field { private set; get; } |
There was a problem hiding this comment.
Nit: you could remove the private set part to make the backing fields readonly.
There was a problem hiding this comment.
that's better. changed to use readonly property.
src/ILCompiler/ILCompiler.sln
Outdated
| Microsoft Visual Studio Solution File, Format Version 12.00 | ||
| # Visual Studio 15 | ||
| VisualStudioVersion = 15.0.26228.9 | ||
| VisualStudioVersion = 15.0.26430.6 |
There was a problem hiding this comment.
Changes to this file seem unnecessary.
There was a problem hiding this comment.
I see VS insisting on similar GUID changes whenever I flip configurations or do something similar. Submitted #3655
There was a problem hiding this comment.
Thanks. I've reverted my change
| <UseDebugLibraries>true</UseDebugLibraries> | ||
| <PlatformToolset>v141</PlatformToolset> | ||
| <!-- v140 is friendly to windbg, at the moment --> | ||
| <PlatformToolset>v140</PlatformToolset> |
There was a problem hiding this comment.
Changes like this should ideally be done in a separate pull request instead of sneaking them in a big change.
There was a problem hiding this comment.
Is this because of the .pdbs have back pointers to .obj files with VS2017? It would be better to pass the linker option that avoids the back pointers to .obj files. @sandreenko Do you happen to know the option?
I would love to be able to always dogfood latest VS - I do not even have pre-VS2017 on most of my machines anymore.
There was a problem hiding this comment.
I'll submit a separate PR for this after a bit more investigation (not my intention to put this in). Someone was suggesting fastlink becoming default as a possibility (not sure if it is the same as back pointers to .obj files) but I haven't got a chance to run more experiment yet.
BTW, this only means VS 2015 toolset is required but you can still use VS 2017. There is still a annoying dialog to ask you to upgrade, though.
…ize of GCStaticEEType
Program.cs and Repro.il are simply there for my testing convenience. I'll remove them before merging.
A few TODOs
@davidwrighton @MichalStrehovsky @nattress PTAL
/cc @fadimounir @hoyMS