File: ILCompiler\Metadata\Transform.Type.cs
Web Access
Project: src\src\runtime\src\coreclr\tools\aot\ILCompiler.MetadataTransform\ILCompiler.MetadataTransform.csproj (ILCompiler.MetadataTransform)
// 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.Metadata.NativeFormat.Writer;

using Ecma = System.Reflection.Metadata;
using Cts = Internal.TypeSystem;

using Debug = System.Diagnostics.Debug;
using TypeAttributes = System.Reflection.TypeAttributes;

namespace ILCompiler.Metadata
{
    internal partial class Transform<TPolicy>
    {
        private const TypeAttributes TypeAttributesExtendedLayout = (TypeAttributes)0x00000018;

        internal EntityMap<Cts.TypeDesc, MetadataRecord> _types =
            new EntityMap<Cts.TypeDesc, MetadataRecord>(EqualityComparer<Cts.TypeDesc>.Default);

        private Action<Cts.MetadataType, TypeDefinition> _initTypeDef;
        private Action<Cts.MetadataType, TypeReference> _initTypeRef;
        private Action<Cts.ArrayType, TypeSpecification> _initSzArray;
        private Action<Cts.ArrayType, TypeSpecification> _initArray;
        private Action<Cts.ByRefType, TypeSpecification> _initByRef;
        private Action<Cts.PointerType, TypeSpecification> _initPointer;
        private Action<Cts.FunctionPointerType, TypeSpecification> _initFunctionPointer;
        private Action<Cts.TypeDesc, TypeSpecification> _initTypeInst;
        private Action<Cts.SignatureTypeVariable, TypeSpecification> _initTypeVar;
        private Action<Cts.SignatureMethodVariable, TypeSpecification> _initMethodVar;

        public override MetadataRecord HandleType(Cts.TypeDesc type)
        {
            MetadataRecord rec;
            if (_types.TryGet(type, out rec))
            {
                return rec;
            }

            switch (type.Category)
            {
                case Cts.TypeFlags.SzArray:
                    rec = _types.Create((Cts.ArrayType)type, _initSzArray ??= InitializeSzArray);
                    break;
                case Cts.TypeFlags.Array:
                    rec = _types.Create((Cts.ArrayType)type, _initArray ??= InitializeArray);
                    break;
                case Cts.TypeFlags.ByRef:
                    rec = _types.Create((Cts.ByRefType)type, _initByRef ??= InitializeByRef);
                    break;
                case Cts.TypeFlags.Pointer:
                    rec = _types.Create((Cts.PointerType)type, _initPointer ??= InitializePointer);
                    break;
                case Cts.TypeFlags.FunctionPointer:
                    rec = _types.Create((Cts.FunctionPointerType)type, _initFunctionPointer ??= InitializeFunctionPointer);
                    break;
                case Cts.TypeFlags.SignatureTypeVariable:
                    rec = _types.Create((Cts.SignatureTypeVariable)type, _initTypeVar ??= InitializeTypeVariable);
                    break;
                case Cts.TypeFlags.SignatureMethodVariable:
                    rec = _types.Create((Cts.SignatureMethodVariable)type, _initMethodVar ??= InitializeMethodVariable);
                    break;
                default:
                    {
                        Debug.Assert(type.IsDefType);

                        if (!type.IsTypeDefinition)
                        {
                            // Instantiated generic type
                            rec = _types.Create(type, _initTypeInst ??= InitializeTypeInstance);
                        }
                        else
                        {
                            // Type definition
                            var metadataType = (Cts.MetadataType)type;
                            if (_policy.GeneratesMetadata(metadataType))
                            {
                                Debug.Assert(!_policy.IsBlocked(metadataType));
                                rec = _types.Create(metadataType, _initTypeDef ??= InitializeTypeDef);
                            }
                            else
                            {
                                rec = _types.Create(metadataType, _initTypeRef ??= InitializeTypeRef);
                            }
                        }
                    }
                    break;
            }

            Debug.Assert(rec is TypeDefinition || rec is TypeReference || rec is TypeSpecification);

            return rec;
        }

        private void InitializeSzArray(Cts.ArrayType entity, TypeSpecification record)
        {
            record.Signature = new SZArraySignature
            {
                ElementType = HandleType(entity.ElementType),
            };
        }

        private void InitializeArray(Cts.ArrayType entity, TypeSpecification record)
        {
            record.Signature = new ArraySignature
            {
                ElementType = HandleType(entity.ElementType),
                Rank = entity.Rank,
                // TODO: LowerBounds
                LowerBounds = Array.Empty<int>(),
                // TODO: Sizes
                Sizes = Array.Empty<int>(),
            };
        }

        private void InitializeByRef(Cts.ByRefType entity, TypeSpecification record)
        {
            record.Signature = new ByReferenceSignature
            {
                Type = HandleType(entity.ParameterType)
            };
        }

        private void InitializePointer(Cts.PointerType entity, TypeSpecification record)
        {
            record.Signature = new PointerSignature
            {
                Type = HandleType(entity.ParameterType)
            };
        }

        private void InitializeFunctionPointer(Cts.FunctionPointerType entity, TypeSpecification record)
        {
            record.Signature = new FunctionPointerSignature
            {
                Signature = HandleMethodSignature(entity.Signature)
            };
        }

        private void InitializeTypeVariable(Cts.SignatureTypeVariable entity, TypeSpecification record)
        {
            record.Signature = new TypeVariableSignature
            {
                Number = entity.Index
            };
        }

        private void InitializeMethodVariable(Cts.SignatureMethodVariable entity, TypeSpecification record)
        {
            record.Signature = new MethodTypeVariableSignature
            {
                Number = entity.Index
            };
        }

        private void InitializeTypeInstance(Cts.TypeDesc entity, TypeSpecification record)
        {
            var sig = new TypeInstantiationSignature
            {
                GenericType = HandleType(entity.GetTypeDefinition()),
            };

            for (int i = 0; i < entity.Instantiation.Length; i++)
            {
                sig.GenericTypeArguments.Add(HandleType(entity.Instantiation[i]));
            }

            record.Signature = sig;
        }

        private TypeReference GetNestedReferenceParent(Cts.MetadataType entity)
        {
            // This special code deals with the metadata format requirement saying that
            // nested type *references* need to have a type *reference* as their containing type.
            // This is potentially in conflict with our other rule that says to always resolve
            // references to their definition records (we are avoiding emitting references
            // to things that have a definition within the same blob to save space).

            Cts.MetadataType containingType = (Cts.MetadataType)entity.ContainingType;
            MetadataRecord parentRecord = HandleType(containingType);
            TypeReference parentReferenceRecord = parentRecord as TypeReference;

            if (parentReferenceRecord != null)
            {
                // Easy case - parent type doesn't have a definition record.
                return parentReferenceRecord;
            }

            // Parent has a type definition record. We need to make a new record that's a reference.
            // We don't bother with interning these because this will be rare and metadata writer
            // will do the interning anyway.
            Debug.Assert(parentRecord is TypeDefinition);

            parentReferenceRecord = new TypeReference
            {
                TypeName = HandleString(containingType.GetName()),
            };

            if (containingType.ContainingType != null)
            {
                parentReferenceRecord.ParentNamespaceOrType = GetNestedReferenceParent(containingType);
            }
            else
            {
                parentReferenceRecord.ParentNamespaceOrType = HandleNamespaceReference(containingType.Module, containingType.GetNamespace());
            }

            return parentReferenceRecord;
        }

        private void InitializeTypeRef(Cts.MetadataType entity, TypeReference record)
        {
            Debug.Assert(entity.IsTypeDefinition);

            if (entity.ContainingType != null)
            {
                record.ParentNamespaceOrType = GetNestedReferenceParent(entity);
            }
            else
            {
                record.ParentNamespaceOrType = HandleNamespaceReference(entity.Module, entity.GetNamespace());
            }

            record.TypeName = HandleString(entity.GetName());
        }

        private void InitializeTypeDef(Cts.MetadataType entity, TypeDefinition record)
        {
            Debug.Assert(entity.IsTypeDefinition);

            Cts.MetadataType containingType = (Cts.MetadataType)entity.ContainingType;
            if (containingType != null)
            {
                var enclosingType = (TypeDefinition)HandleType(containingType);
                record.EnclosingType = enclosingType;
                enclosingType.NestedTypes.Add(record);

                var namespaceDefinition =
                    HandleNamespaceDefinition(containingType.Module, entity.ContainingType.GetNamespace());
                record.NamespaceDefinition = namespaceDefinition;
            }
            else
            {
                var namespaceDefinition = HandleNamespaceDefinition(entity.Module, entity.GetNamespace());
                record.NamespaceDefinition = namespaceDefinition;

                if (entity.IsModuleType)
                {
                    // These don't get added to the global namespace.
                    // Instead, they have a dedicated field on the scope record.
                }
                else
                {
                    namespaceDefinition.TypeDefinitions.Add(record);
                }
            }

            record.Name = HandleString(entity.GetName());

            Cts.ClassLayoutMetadata layoutMetadata = entity.GetClassLayout();
            record.Size = checked((uint)layoutMetadata.Size);
            record.PackingSize = checked((ushort)layoutMetadata.PackingSize);
            record.Flags = GetTypeAttributes(entity);

            try
            {
                if (entity.HasBaseType)
                {
                    record.BaseType = HandleType(entity.BaseType);
                }
            }
            catch (Cts.TypeSystemException) when (HasNestedTypes(entity))
            {
                // We might have been forced to generate metadata for a type
                // that wasn't looked at during code generation because it's an owning
                // type of a type we did look at. Allow those to generate incomplete
                // metadata. The ultimate fix is to rewrite metadata generation to be
                // System.Reflection.Metadata-based as opposed to type system based.
                // If there's no nested types, this is a bug and should tear down
                // the compiler at this point.
            }

            try
            {
                record.Interfaces.Capacity = entity.ExplicitlyImplementedInterfaces.Length;
                foreach (var interfaceType in entity.ExplicitlyImplementedInterfaces)
                {
                    if (IsBlocked(interfaceType))
                        continue;
                    if (!_policy.GeneratesInterfaceImpl(entity, (Cts.MetadataType)interfaceType))
                        continue;

                    record.Interfaces.Add(HandleType(interfaceType));
                }
            }
            catch (Cts.TypeSystemException) when (HasNestedTypes(entity))
            {
                // We might have been forced to generate metadata for a type
                // that wasn't looked at during code generation because it's an owning
                // type of a type we did look at. Allow those to generate incomplete
                // metadata. The ultimate fix is to rewrite metadata generation to be
                // System.Reflection.Metadata-based as opposed to type system based.
                // If there's no nested types, this is a bug and should tear down
                // the compiler at this point.
            }

            if (entity.HasInstantiation)
            {
                record.GenericParameters.Capacity = entity.Instantiation.Length;
                foreach (var p in entity.Instantiation)
                    record.GenericParameters.Add(HandleGenericParameter((Cts.GenericParameterDesc)p));
            }

            foreach (var field in entity.GetFields())
            {
                if (_policy.GeneratesMetadata(field))
                {
                    record.Fields.Add(HandleFieldDefinition(field));
                }
            }

            foreach (var method in entity.GetMethods())
            {
                if (_policy.GeneratesMetadata(method))
                {
                    record.Methods.Add(HandleMethodDefinition(method));
                }
            }

            var ecmaEntity = entity as Cts.Ecma.EcmaType;
            if (ecmaEntity != null)
            {
                Ecma.TypeDefinition ecmaRecord = ecmaEntity.MetadataReader.GetTypeDefinition(ecmaEntity.Handle);

                foreach (var e in ecmaRecord.GetEvents())
                {
                    Event evt = HandleEvent(ecmaEntity.Module, e);
                    if (evt != null)
                        record.Events.Add(evt);
                }

                foreach (var property in ecmaRecord.GetProperties())
                {
                    Property prop = HandleProperty(ecmaEntity.Module, property);
                    if (prop != null)
                        record.Properties.Add(prop);
                }

                Ecma.CustomAttributeHandleCollection customAttributes = ecmaRecord.GetCustomAttributes();
                if (customAttributes.Count > 0)
                {
                    record.CustomAttributes = HandleCustomAttributes(ecmaEntity.Module, customAttributes);
                }

                /* COMPLETENESS
                foreach (var miHandle in ecmaRecord.GetMethodImplementations())
                {
                    Ecma.MetadataReader reader = ecmaEntity.Module.MetadataReader;

                    Ecma.MethodImplementation miDef = reader.GetMethodImplementation(miHandle);

                    Cts.MethodDesc methodBody = (Cts.MethodDesc)ecmaEntity.Module.GetObject(miDef.MethodBody);
                    if (_policy.IsBlocked(methodBody))
                        continue;

                    Cts.MethodDesc methodDecl = (Cts.MethodDesc)ecmaEntity.Module.GetObject(miDef.MethodDeclaration);
                    if (_policy.IsBlocked(methodDecl.GetTypicalMethodDefinition()))
                        continue;

                    MethodImpl methodImplRecord = new MethodImpl
                    {
                        MethodBody = HandleQualifiedMethod(methodBody),
                        MethodDeclaration = HandleQualifiedMethod(methodDecl)
                    };

                    record.MethodImpls.Add(methodImplRecord);
                }*/
            }

            static bool HasNestedTypes(Cts.MetadataType entity)
                => entity.GetNestedTypes().GetEnumerator().MoveNext();
        }

        private MetadataRecord HandleType(Cts.Ecma.EcmaModule module, ref Ecma.BlobReader reader)
        {
            switch (reader.ReadSignatureTypeCode())
            {
                case Ecma.SignatureTypeCode.Void:
                    return HandleType(module.Context.GetWellKnownType(Cts.WellKnownType.Void));
                case Ecma.SignatureTypeCode.Boolean:
                    return HandleType(module.Context.GetWellKnownType(Cts.WellKnownType.Boolean));
                case Ecma.SignatureTypeCode.SByte:
                    return HandleType(module.Context.GetWellKnownType(Cts.WellKnownType.SByte));
                case Ecma.SignatureTypeCode.Byte:
                    return HandleType(module.Context.GetWellKnownType(Cts.WellKnownType.Byte));
                case Ecma.SignatureTypeCode.Int16:
                    return HandleType(module.Context.GetWellKnownType(Cts.WellKnownType.Int16));
                case Ecma.SignatureTypeCode.UInt16:
                    return HandleType(module.Context.GetWellKnownType(Cts.WellKnownType.UInt16));
                case Ecma.SignatureTypeCode.Int32:
                    return HandleType(module.Context.GetWellKnownType(Cts.WellKnownType.Int32));
                case Ecma.SignatureTypeCode.UInt32:
                    return HandleType(module.Context.GetWellKnownType(Cts.WellKnownType.UInt32));
                case Ecma.SignatureTypeCode.Int64:
                    return HandleType(module.Context.GetWellKnownType(Cts.WellKnownType.Int64));
                case Ecma.SignatureTypeCode.UInt64:
                    return HandleType(module.Context.GetWellKnownType(Cts.WellKnownType.UInt64));
                case Ecma.SignatureTypeCode.Single:
                    return HandleType(module.Context.GetWellKnownType(Cts.WellKnownType.Single));
                case Ecma.SignatureTypeCode.Double:
                    return HandleType(module.Context.GetWellKnownType(Cts.WellKnownType.Double));
                case Ecma.SignatureTypeCode.Char:
                    return HandleType(module.Context.GetWellKnownType(Cts.WellKnownType.Char));
                case Ecma.SignatureTypeCode.String:
                    return HandleType(module.Context.GetWellKnownType(Cts.WellKnownType.String));
                case Ecma.SignatureTypeCode.IntPtr:
                    return HandleType(module.Context.GetWellKnownType(Cts.WellKnownType.IntPtr));
                case Ecma.SignatureTypeCode.UIntPtr:
                    return HandleType(module.Context.GetWellKnownType(Cts.WellKnownType.UIntPtr));
                case Ecma.SignatureTypeCode.Object:
                    return HandleType(module.Context.GetWellKnownType(Cts.WellKnownType.Object));
                case Ecma.SignatureTypeCode.TypeHandle:
                    return HandleType(module.GetType(reader.ReadTypeHandle()));
                case Ecma.SignatureTypeCode.SZArray:
                    return new TypeSpecification
                    {
                        Signature = new SZArraySignature()
                        {
                            ElementType = HandleType(module, ref reader)
                        }
                    };
                case Ecma.SignatureTypeCode.Array:
                    {
                        MetadataRecord elementType = HandleType(module, ref reader);
                        int rank = reader.ReadCompressedInteger();

                        var boundsCount = reader.ReadCompressedInteger();
                        for (int i = 0; i < boundsCount; i++)
                            reader.ReadCompressedInteger();
                        var lowerBoundsCount = reader.ReadCompressedInteger();
                        for (int j = 0; j < lowerBoundsCount; j++)
                            reader.ReadCompressedSignedInteger();

                        return new TypeSpecification
                        {
                            Signature = new ArraySignature()
                            {
                                ElementType = elementType,
                                Rank = rank,
                                // TODO: LowerBounds
                                LowerBounds = Array.Empty<int>(),
                                // TODO: Sizes
                                Sizes = Array.Empty<int>(),
                            }
                        };
                    }
                case Ecma.SignatureTypeCode.ByReference:
                    return new TypeSpecification
                    {
                        Signature = new ByReferenceSignature()
                        {
                            Type = HandleType(module, ref reader)
                        }
                    };
                case Ecma.SignatureTypeCode.Pointer:
                    return new TypeSpecification
                    {
                        Signature = new PointerSignature()
                        {
                            Type = HandleType(module, ref reader)
                        }
                    };
                case Ecma.SignatureTypeCode.GenericTypeParameter:
                    return HandleType(module.Context.GetSignatureVariable(reader.ReadCompressedInteger(), false));
                case Ecma.SignatureTypeCode.GenericMethodParameter:
                    return HandleType(module.Context.GetSignatureVariable(reader.ReadCompressedInteger(), true));
                case Ecma.SignatureTypeCode.GenericTypeInstance:
                    {
                        var sig = new TypeInstantiationSignature
                        {
                            GenericType = HandleType(module, ref reader)
                        };

                        int count = reader.ReadCompressedInteger();
                        for (int i = 0; i < count; i++)
                            sig.GenericTypeArguments.Add(HandleType(module, ref reader));

                        return new TypeSpecification
                        {
                            Signature = sig
                        };
                    }
                case Ecma.SignatureTypeCode.TypedReference:
                    return HandleType(module.Context.GetWellKnownType(Cts.WellKnownType.TypedReference));
                case Ecma.SignatureTypeCode.FunctionPointer:
                    return new TypeSpecification
                    {
                        Signature = new FunctionPointerSignature
                        {
                            Signature = HandleMethodSignature(module, ref reader)
                        }
                    };
                case Ecma.SignatureTypeCode.OptionalModifier:
                    return new ModifiedType
                    {
                        IsOptional = true,
                        ModifierType = HandleType(module.GetType(reader.ReadTypeHandle())),
                        Type = HandleType(module, ref reader),
                    };
                case Ecma.SignatureTypeCode.RequiredModifier:
                    return new ModifiedType
                    {
                        IsOptional = false,
                        ModifierType = HandleType(module.GetType(reader.ReadTypeHandle())),
                        Type = HandleType(module, ref reader),
                    };

                default:
                    throw new BadImageFormatException();
            }
        }

        private static TypeAttributes GetTypeAttributes(Cts.MetadataType type)
        {
            TypeAttributes result;

            var ecmaType = type as Cts.Ecma.EcmaType;
            if (ecmaType != null)
            {
                Ecma.TypeDefinition ecmaRecord = ecmaType.MetadataReader.GetTypeDefinition(ecmaType.Handle);
                result = ecmaRecord.Attributes;
            }
            else
            {
                result = 0;

                if (type.IsExplicitLayout)
                    result |= TypeAttributes.ExplicitLayout;
                if (type.IsSequentialLayout)
                    result |= TypeAttributes.SequentialLayout;
                if (type.IsExtendedLayout)
                    result |= TypeAttributesExtendedLayout;
                if (type.IsInterface)
                    result |= TypeAttributes.Interface;
                if (type.IsSealed)
                    result |= TypeAttributes.Sealed;
                if (type.IsBeforeFieldInit)
                    result |= TypeAttributes.BeforeFieldInit;

                // Not set: Abstract, Ansi/Unicode/Auto, HasSecurity, Import, visibility, Serializable,
                //          WindowsRuntime, HasSecurity, SpecialName, RTSpecialName
            }

            return result;
        }
    }
}