File: CodeGen\EmitStackAllocInitializer.cs
Web Access
Project: ..\..\..\src\Compilers\CSharp\Portable\Microsoft.CodeAnalysis.CSharp.csproj (Microsoft.CodeAnalysis.CSharp)
// 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.
 
#nullable disable
 
using System;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using System.Reflection.Metadata;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.PooledObjects;
 
namespace Microsoft.CodeAnalysis.CSharp.CodeGen
{
    internal partial class CodeGenerator
    {
        private void EmitStackAllocInitializers(TypeSymbol type, BoundArrayInitialization inits)
        {
            Debug.Assert(type is PointerTypeSymbol || type is NamedTypeSymbol);
 
            var elementType = (type.TypeKind == TypeKind.Pointer
                ? ((PointerTypeSymbol)type).PointedAtTypeWithAnnotations
                : ((NamedTypeSymbol)type).TypeArgumentsWithAnnotationsNoUseSiteDiagnostics[0]).Type;
 
            var initExprs = inits.Initializers;
 
            var initializationStyle = ShouldEmitBlockInitializerForStackAlloc(elementType, initExprs);
            if (initializationStyle == ArrayInitializerStyle.Element)
            {
                EmitElementStackAllocInitializers(elementType, initExprs, includeConstants: true);
            }
            else
            {
                ImmutableArray<byte> data = this.GetRawData(initExprs);
                if (data.All(datum => datum == data[0]))
                {
                    // All bytes are the same, no need for metadata blob, just initblk to fill it with the repeated value.
                    _builder.EmitOpCode(ILOpCode.Dup);
                    _builder.EmitIntConstant(data[0]);
                    _builder.EmitIntConstant(data.Length);
                    _builder.EmitOpCode(ILOpCode.Initblk, -3);
 
                    if (initializationStyle == ArrayInitializerStyle.Mixed)
                    {
                        EmitElementStackAllocInitializers(elementType, initExprs, includeConstants: false);
                    }
                }
                else if (elementType.EnumUnderlyingTypeOrSelf().SpecialType.SizeInBytes() == 1)
                {
                    // Initialize the stackalloc by copying the data from a metadata blob
                    var field = _builder.module.GetFieldForData(data, alignment: 1, inits.Syntax, _diagnostics.DiagnosticBag);
                    _builder.EmitOpCode(ILOpCode.Dup);
                    _builder.EmitOpCode(ILOpCode.Ldsflda);
                    _builder.EmitToken(field, inits.Syntax, _diagnostics.DiagnosticBag);
                    _builder.EmitIntConstant(data.Length);
                    _builder.EmitOpCode(ILOpCode.Cpblk, -3);
 
                    if (initializationStyle == ArrayInitializerStyle.Mixed)
                    {
                        EmitElementStackAllocInitializers(elementType, initExprs, includeConstants: false);
                    }
                }
                else
                {
                    EmitElementStackAllocInitializers(elementType, initExprs, includeConstants: true);
                }
            }
        }
 
        private ArrayInitializerStyle ShouldEmitBlockInitializerForStackAlloc(TypeSymbol elementType, ImmutableArray<BoundExpression> inits)
        {
            if (!_module.SupportsPrivateImplClass)
            {
                return ArrayInitializerStyle.Element;
            }
 
            if (elementType.EnumUnderlyingTypeOrSelf().SpecialType.IsBlittable())
            {
                int initCount = 0;
                int constCount = 0;
                StackAllocInitializerCount(inits, ref initCount, ref constCount);
 
                if (initCount > 2)
                {
                    if (initCount == constCount)
                    {
                        return ArrayInitializerStyle.Block;
                    }
 
                    int thresholdCnt = Math.Max(3, (initCount / 3));
 
                    if (constCount >= thresholdCnt)
                    {
                        return ArrayInitializerStyle.Mixed;
                    }
                }
            }
 
            return ArrayInitializerStyle.Element;
        }
 
        private void StackAllocInitializerCount(ImmutableArray<BoundExpression> inits, ref int initCount, ref int constInits)
        {
            if (inits.Length == 0)
            {
                return;
            }
 
            foreach (var init in inits)
            {
                Debug.Assert(!(init is BoundArrayInitialization), "Nested initializers are not allowed for stackalloc");
 
                initCount += 1;
                if (init.ConstantValueOpt != null)
                {
                    constInits += 1;
                }
            }
        }
 
        private void EmitElementStackAllocInitializers(TypeSymbol elementType, ImmutableArray<BoundExpression> inits, bool includeConstants)
        {
            int index = 0;
            int elementTypeSizeInBytes = elementType.EnumUnderlyingTypeOrSelf().SpecialType.SizeInBytes();
            foreach (BoundExpression init in inits)
            {
                if (includeConstants || init.ConstantValueOpt == null)
                {
                    _builder.EmitOpCode(ILOpCode.Dup);
                    EmitPointerElementAccess(init, elementType, elementTypeSizeInBytes, index);
                    EmitExpression(init, used: true);
                    EmitIndirectStore(elementType, init.Syntax);
                }
 
                index++;
            }
        }
 
        private void EmitPointerElementAccess(BoundExpression init, TypeSymbol elementType, int elementTypeSizeInBytes, int index)
        {
            if (index == 0)
            {
                return;
            }
 
            if (elementTypeSizeInBytes == 1)
            {
                _builder.EmitIntConstant(index);
                _builder.EmitOpCode(ILOpCode.Add);
            }
            else if (index == 1)
            {
                EmitIntConstantOrSizeOf(init, elementType, elementTypeSizeInBytes);
                _builder.EmitOpCode(ILOpCode.Add);
            }
            else
            {
                _builder.EmitIntConstant(index);
                _builder.EmitOpCode(ILOpCode.Conv_i);
                EmitIntConstantOrSizeOf(init, elementType, elementTypeSizeInBytes);
                _builder.EmitOpCode(ILOpCode.Mul);
                _builder.EmitOpCode(ILOpCode.Add);
            }
        }
 
        private void EmitIntConstantOrSizeOf(BoundExpression init, TypeSymbol elementType, int elementTypeSizeInBytes)
        {
            if (elementTypeSizeInBytes == 0)
            {
                _builder.EmitOpCode(ILOpCode.Sizeof);
                EmitSymbolToken(elementType, init.Syntax);
            }
            else
            {
                _builder.EmitIntConstant(elementTypeSizeInBytes);
            }
        }
    }
}