Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 8 additions & 14 deletions src/Compilers/CSharp/Portable/CodeGen/EmitExpression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2367,24 +2367,18 @@ private void EmitArrayCreationExpression(BoundArrayCreation expression, bool use

private void EmitConvertedStackAllocExpression(BoundConvertedStackAllocExpression expression, bool used)
{
EmitExpression(expression.Count, used);

// the only sideeffect of a localloc is a nondeterministic and generally fatal StackOverflow.
// we can ignore that if the actual result is unused
var initializer = expression.InitializerOpt;
if (used)
{
_sawStackalloc = true;
_builder.EmitOpCode(ILOpCode.Localloc);
EmitStackAlloc(expression.Type, initializer, expression.Count);
}

var initializer = expression.InitializerOpt;
if (initializer != null)
else
{
if (used)
{
EmitStackAllocInitializers(expression.Type, initializer);
}
else
// the only sideeffect of a localloc is a nondeterministic and generally fatal StackOverflow.
// we can ignore that if the actual result is unused
EmitExpression(expression.Count, used: false);

if (initializer != null)
{
// If not used, just emit initializer elements to preserve possible sideeffects
foreach (var init in initializer.Initializers)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,27 @@
// 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)
private void EmitStackAlloc(TypeSymbol type, BoundArrayInitialization? inits, BoundExpression count)
{
if (inits is null)
{
emitLocalloc();
return;
}

Debug.Assert(type is PointerTypeSymbol || type is NamedTypeSymbol);
Debug.Assert(_diagnostics.DiagnosticBag is not null);

var elementType = (type.TypeKind == TypeKind.Pointer
? ((PointerTypeSymbol)type).PointedAtTypeWithAnnotations
Expand All @@ -29,44 +33,97 @@ private void EmitStackAllocInitializers(TypeSymbol type, BoundArrayInitializatio
var initializationStyle = ShouldEmitBlockInitializerForStackAlloc(elementType, initExprs);
if (initializationStyle == ArrayInitializerStyle.Element)
{
emitLocalloc();
EmitElementStackAllocInitializers(elementType, initExprs, includeConstants: true);
}
else
{
ImmutableArray<byte> data = this.GetRawData(initExprs);
bool mixedInitialized = false;

emitLocalloc();

var sizeInBytes = elementType.EnumUnderlyingTypeOrSelf().SpecialType.SizeInBytes();

ImmutableArray<byte> data = 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)
else if (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.EmitUnaligned(alignment: 1);
_builder.EmitOpCode(ILOpCode.Cpblk, -3);

if (initializationStyle == ArrayInitializerStyle.Mixed)
}
else
{
var syntaxNode = inits.Syntax;
if (Binder.GetWellKnownTypeMember(_module.Compilation, WellKnownMember.System_Runtime_CompilerServices_RuntimeHelpers__CreateSpanRuntimeFieldHandle, _diagnostics, syntax: syntaxNode, isOptional: true) is MethodSymbol createSpanHelper &&
Binder.GetWellKnownTypeMember(_module.Compilation, WellKnownMember.System_ReadOnlySpan_T__get_Item, _diagnostics, syntax: syntaxNode, isOptional: true) is MethodSymbol spanGetItemDefinition)
{
EmitElementStackAllocInitializers(elementType, initExprs, includeConstants: false);
// Use RuntimeHelpers.CreateSpan and cpblk.
var readOnlySpan = spanGetItemDefinition.ContainingType.Construct(elementType);
Debug.Assert(TypeSymbol.Equals(readOnlySpan.OriginalDefinition, _module.Compilation.GetWellKnownType(WellKnownType.System_ReadOnlySpan_T), TypeCompareKind.ConsiderEverything));
var spanGetItem = spanGetItemDefinition.AsMember(readOnlySpan);

_builder.EmitOpCode(ILOpCode.Dup);

// ldtoken <PrivateImplementationDetails>...
// call ReadOnlySpan<elementType> RuntimeHelpers::CreateSpan<elementType>(fldHandle)
var field = _builder.module.GetFieldForData(data, alignment: (ushort)sizeInBytes, syntaxNode, _diagnostics.DiagnosticBag);
_builder.EmitOpCode(ILOpCode.Ldtoken);
_builder.EmitToken(field, syntaxNode, _diagnostics.DiagnosticBag);
_builder.EmitOpCode(ILOpCode.Call, 0);
var createSpanHelperReference = createSpanHelper.Construct(elementType).GetCciAdapter();
_builder.EmitToken(createSpanHelperReference, syntaxNode, _diagnostics.DiagnosticBag);

var temp = AllocateTemp(readOnlySpan, syntaxNode);
_builder.EmitLocalStore(temp);
_builder.EmitLocalAddress(temp);

// span.get_Item[0]
_builder.EmitIntConstant(0);
_builder.EmitOpCode(ILOpCode.Call, 0);
EmitSymbolToken(spanGetItem, syntaxNode, optArgList: null);

_builder.EmitIntConstant(data.Length);
if (sizeInBytes != 8)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if (sizeInBytes != 8)

Looking at the unaligned. section of ECMA-335

shall use unaligned. if the alignment is not known at compile time to be 8-byte.

From that, I infer that, if we align the RVA field type at 8 bytes, we will be compliant without the unaligned prefix. Should we use that option instead, rather than having conditional IL?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are there any downsides to conditional IL? On the other hand, it seems that aligning properly to 1/2/4/8 bytes instead of always to 8 bytes saves space.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are there any downsides to conditional IL?

IL is bigger. Compiler's code is more complex. Test matrix is bigger, more scenarios need special attention.

it seems that aligning properly to 1/2/4/8 bytes instead of always to 8 bytes saves space.

I do not think it actually does. At least not for the code emitted by our compilers because, I think, compiler aligns all blobs at 8 bytes regardless of the requested alignment, this is mentioned in one of the comments in code. The alignment that we explicitly set on the types is important for IL rewriting scenarios.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The alignment that we explicitly set on the types is important for IL rewriting scenarios.

I think this means it improves size for AOT and similar precompiled code (right?) which seems important as well.

Copy link
Contributor

@AlekseyTs AlekseyTs Jan 10, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this means it improves size for AOT and similar precompiled code (right?) which seems important as well.

To be honest, I do not know the answer to these questions, And, even if that was the case, I am not sure if that is something that we should care about.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not think it makes sense to deoptimize the 1-byte case like that.

To make the two cases as similar as possible, you can emit unaligned.1 cpblk in both cases and emit the blob with elementType.EnumUnderlyingTypeOrSelf().SpecialType.SizeInBytes() alignment in both cases.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

did you want that to be aligned to 8 bytes as well?

Yes, this is what I would do. I would align both at 8 bytes explicitly (we are aligning blobs at 8 bytes regardless), and would completely get rid of the .unaligned instruction.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would align both at 8 bytes explicitly (we are aligning blobs at 8 bytes regardless), and would completely get rid of the .unaligned instruction.

It is size de-optimization for trimmer and AOT that are scenarios where we care about the side the most.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is size de-optimization for trimmer and AOT that are scenarios where we care about the side the most.

Perhaps I misinterpreted you first message on this discussion thread.

If the blob size optimization is important for AOT, then, I think we should keep the .unaligned usage on both code paths. We can get there by reverting the last commit. Sorry for the confusion.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps I misinterpreted you first message on this discussion thread.

I was commenting on why we set the alignment and why it would not work to omit it on the types. Sorry for the confusion.

{
_builder.EmitUnaligned((sbyte)sizeInBytes);
}
_builder.EmitOpCode(ILOpCode.Cpblk, -3);

FreeTemp(temp);
}
else
{
EmitElementStackAllocInitializers(elementType, initExprs, includeConstants: true);
mixedInitialized = true;
}
}
else

if (initializationStyle == ArrayInitializerStyle.Mixed && !mixedInitialized)
{
EmitElementStackAllocInitializers(elementType, initExprs, includeConstants: true);
EmitElementStackAllocInitializers(elementType, initExprs, includeConstants: false);
}
}

void emitLocalloc()
{
EmitExpression(count, used: true);

_sawStackalloc = true;
_builder.EmitOpCode(ILOpCode.Localloc);
}
}

private ArrayInitializerStyle ShouldEmitBlockInitializerForStackAlloc(TypeSymbol elementType, ImmutableArray<BoundExpression> inits)
Expand All @@ -78,7 +135,7 @@ private ArrayInitializerStyle ShouldEmitBlockInitializerForStackAlloc(TypeSymbol
return ArrayInitializerStyle.Element;
}

if (elementType.EnumUnderlyingTypeOrSelf().SpecialType.IsBlittable())
if (IsTypeAllowedInBlobWrapper(elementType.EnumUnderlyingTypeOrSelf().SpecialType))
{
int initCount = 0;
int constCount = 0;
Expand Down
Loading