77using System ;
88using System . Collections . Immutable ;
99using System . Diagnostics ;
10+ using System . Diagnostics . CodeAnalysis ;
1011using System . Linq ;
1112using System . Reflection . Metadata ;
1213using Microsoft . CodeAnalysis . CSharp . Symbols ;
@@ -449,6 +450,13 @@ private bool TryEmitReadonlySpanAsBlobWrapper(NamedTypeSymbol spanType, BoundExp
449450return false ;
450451}
451452
453+ if ( inPlaceTarget is null && ! used )
454+ {
455+ // The caller has specified that we're creating a ReadOnlySpan expression that won't be used.
456+ // We needn't emit anything.
457+ return true ;
458+ }
459+
452460// The primary optimization here is for byte-sized primitives that can wrap a ReadOnlySpan directly around a pointer
453461// into a blob. That requires the ReadOnlySpan(void*, int) ctor. If this constructor isn't available, we give up on
454462// all optimizations. Technically, if this ctor isn't available but the ReadOnlySpan(T[]) constructor is, we could still
@@ -475,7 +483,7 @@ private bool TryEmitReadonlySpanAsBlobWrapper(NamedTypeSymbol spanType, BoundExp
475483specialElementType = elementType . EnumUnderlyingTypeOrSelf ( ) . SpecialType ;
476484if ( ! IsTypeAllowedInBlobWrapper ( specialElementType ) )
477485{
478- return false ;
486+ return start is null && length is null && tryEmitAsCachedArrayOfConstants ( ac , arrayType , elementType , spanType , used , inPlaceTarget ) ;
479487}
480488
481489// Get the data and number of elements that compose the initialization.
@@ -522,35 +530,9 @@ private bool TryEmitReadonlySpanAsBlobWrapper(NamedTypeSymbol spanType, BoundExp
522530lengthForConstructor = elementCount ;
523531}
524532
525- if ( inPlaceTarget is null && ! used )
526- {
527- // The caller has specified that we're creating a ReadOnlySpan expression that won't be used.
528- // We needn't emit anything.
529- return true ;
530- }
531-
532533if ( elementCount == 0 )
533534{
534- // The span is empty. Optimize away the array. This works regardless of the size of the type.
535- // (We could optimize this even for non-primitives, but it's not currently worthwhile.)
536-
537- // If this is in-place initialization, call the default ctor.
538- if ( inPlaceTarget is notnull )
539- {
540- EmitAddress ( inPlaceTarget , Binder . AddressKind . Writeable ) ;
541- _builder . EmitOpCode ( ILOpCode . Initobj ) ;
542- EmitSymbolToken ( spanType , wrappedExpression . Syntax ) ;
543- if ( used )
544- {
545- EmitExpression ( inPlaceTarget , used : true ) ;
546- }
547- }
548- else
549- {
550- // Otherwise, assign it to a default value / empty span.
551- Debug . Assert ( used ) ;
552- EmitDefaultValue ( spanType , used , wrappedExpression . Syntax ) ;
553- }
535+ emitEmptyReadonlySpan ( spanType , wrappedExpression , used , inPlaceTarget ) ;
554536return true ;
555537}
556538
@@ -607,7 +589,7 @@ private bool TryEmitReadonlySpanAsBlobWrapper(NamedTypeSymbol spanType, BoundExp
607589// We need to use RuntimeHelpers.CreateSpan / cached array, but the code has requested a subset of the elements.
608590// That means the code is something like `new ReadOnlySpan<char>(new[] { 'a', 'b', 'c' }, 1, 2)`
609591// rather than `new ReadOnlySpan<char>(new[] { 'b', 'c' })`. If such a pattern is found to be
610- // common, this could be augmented toaccomodate it. For now, we just return false to fail
592+ // common, this could be augmented toaccommodate it. For now, we just return false to fail
611593// to optimize this case.
612594return false ;
613595}
@@ -643,55 +625,151 @@ private bool TryEmitReadonlySpanAsBlobWrapper(NamedTypeSymbol spanType, BoundExp
643625// We're dealing with a multi-byte primitive, and CreateSpan was not available. Get a static field from PrivateImplementationDetails,
644626// and use it as a lazily-initialized cache for an array for this data:
645627// new ReadOnlySpan<T>(PrivateImplementationDetails.ArrayField ??= RuntimeHelpers.InitializeArray(new int[Length], PrivateImplementationDetails.DataField));
628+ return emitAsCachedArrayFromBlob ( spanType , wrappedExpression , elementCount , data , ref arrayType , elementType ) ;
646629
647- var rosArrayCtor = ( MethodSymbol ? ) Binder . GetWellKnownTypeMember ( _module . Compilation , WellKnownMember . System_ReadOnlySpan_T__ctor_Array , _diagnostics , syntax : wrappedExpression . Syntax , isOptional : true ) ;
648- if ( rosArrayCtor is null )
630+ // Emit: new ReadOnlySpan<T>(PrivateImplementationDetails.ArrayField ??= RuntimeHelpers.InitializeArray(new int[Length], PrivateImplementationDetails.DataField) );
631+ bool emitAsCachedArrayFromBlob ( NamedTypeSymbol spanType , BoundExpression wrappedExpression , int elementCount , ImmutableArray < byte > data , ref ArrayTypeSymbol arrayType , TypeSymbol elementType )
649632{
650- // The ReadOnlySpan<T>(T[] array) constructor we need is missing or something went wrong.
651- return false ;
633+ if ( ! tryGetReadOnlySpanArrayCtor ( wrappedExpression . Syntax , out var rosArrayCtor ) )
634+ {
635+ return false ;
636+ }
637+
638+ // If we're dealing with an array of enums, we need to handle the possibility that the data blob
639+ // is the same for multiple enums all with the same underlying type, or even with the underlying type
640+ // itself. This is addressed by always caching an array for the underlying type, and then relying on
641+ // arrays being covariant between the underlying type and the enum type, so that it's safe to do:
642+ // new ReadOnlySpan<EnumType>(arrayOfUnderlyingType);
643+ // It's important to have a consistent type here, as otherwise the type of the caching field could
644+ // end up changing non-deterministically based on which type for a given blob was encountered first.
645+ // Also, even if we're not dealing with an enum, we still create a new array type that drops any
646+ // annotations that may have initially been associated with the element type; this is similarly to
647+ // ensure deterministic behavior.
648+ arrayType = arrayType . WithElementType ( TypeWithAnnotations . Create ( elementType . EnumUnderlyingTypeOrSelf ( ) ) ) ;
649+
650+ var cachingField = _builder . module . GetArrayCachingFieldForData ( data , _module . Translate ( arrayType ) , wrappedExpression . Syntax , _diagnostics . DiagnosticBag ) ;
651+ var arrayNotNullLabel = new object ( ) ;
652+
653+ // T[]? array = PrivateImplementationDetails.cachingField;
654+ // if (array is not null) goto arrayNotNull;
655+ _builder . EmitOpCode ( ILOpCode . Ldsfld ) ;
656+ _builder . EmitToken ( cachingField , wrappedExpression . Syntax , _diagnostics . DiagnosticBag ) ;
657+ _builder . EmitOpCode ( ILOpCode . Dup ) ;
658+ _builder . EmitBranch ( ILOpCode . Brtrue , arrayNotNullLabel ) ;
659+
660+ // array = new T[elementCount];
661+ // RuntimeHelpers.InitializeArray(token, array);
662+ // PrivateImplementationDetails.cachingField = array;
663+ _builder . EmitOpCode ( ILOpCode . Pop ) ;
664+ _builder . EmitIntConstant ( elementCount ) ;
665+ _builder . EmitOpCode ( ILOpCode . Newarr ) ;
666+ EmitSymbolToken ( arrayType . ElementType , wrappedExpression . Syntax ) ;
667+ _builder . EmitArrayBlockInitializer ( data , wrappedExpression . Syntax , _diagnostics . DiagnosticBag ) ;
668+ _builder . EmitOpCode ( ILOpCode . Dup ) ;
669+ _builder . EmitOpCode ( ILOpCode . Stsfld ) ;
670+ _builder . EmitToken ( cachingField , wrappedExpression . Syntax , _diagnostics . DiagnosticBag ) ;
671+
672+ // arrayNotNullLabel:
673+ // new ReadOnlySpan<T>(array)
674+ _builder . MarkLabel ( arrayNotNullLabel ) ;
675+ _builder . EmitOpCode ( ILOpCode . Newobj , 0 ) ;
676+ EmitSymbolToken ( rosArrayCtor . AsMember ( spanType ) , wrappedExpression . Syntax , optArgList : null ) ;
677+ return true ;
678+ }
679+
680+ // Emit: new ReadOnlySpan<ElementType>(PrivateImplementationDetails.cachingField ??= new ElementType[] { ... constants ... })
681+ bool tryEmitAsCachedArrayOfConstants ( BoundArrayCreation arrayCreation , ArrayTypeSymbol arrayType , TypeSymbol elementType , NamedTypeSymbol spanType , bool used , BoundExpression ? inPlaceTarget )
682+ {
683+ var initializer = arrayCreation . InitializerOpt ;
684+ if ( initializer == null )
685+ {
686+ return false ;
687+ }
688+
689+ var initializers = initializer . Initializers ;
690+ if ( initializers . Any ( static init=> init . ConstantValueOpt == null ) )
691+ {
692+ return false ;
693+ }
694+
695+ if ( ! tryGetReadOnlySpanArrayCtor ( arrayCreation . Syntax , out var rosArrayCtor ) )
696+ {
697+ return false ;
698+ }
699+
700+ Debug . Assert ( ! elementType . IsEnumType ( ) ) ;
701+
702+ ImmutableArray < ConstantValue > constants = initializers . Select ( static init=> init . ConstantValueOpt ! ) . ToImmutableArray ( ) ;
703+
704+ if ( constants . IsEmpty )
705+ {
706+ emitEmptyReadonlySpan ( spanType , arrayCreation , used , inPlaceTarget ) ;
707+ return true ;
708+ }
709+
710+ Cci . IFieldReference cachingField = _builder . module . GetArrayCachingFieldForConstants ( constants , _module . Translate ( arrayType ) ,
711+ arrayCreation . Syntax , _diagnostics . DiagnosticBag ) ;
712+
713+ var arrayNotNullLabel = new object ( ) ;
714+
715+ // T[]? array = PrivateImplementationDetails.cachingField;
716+ // if (array is not null) goto arrayNotNull;
717+ _builder . EmitOpCode ( ILOpCode . Ldsfld ) ;
718+ _builder . EmitToken ( cachingField , arrayCreation . Syntax , _diagnostics . DiagnosticBag ) ;
719+ _builder . EmitOpCode ( ILOpCode . Dup ) ;
720+ _builder . EmitBranch ( ILOpCode . Brtrue , arrayNotNullLabel ) ;
721+
722+ // array = arrayCreation;
723+ // PrivateImplementationDetails.cachingField = array;
724+ _builder . EmitOpCode ( ILOpCode . Pop ) ;
725+ EmitExpression ( arrayCreation , used : true ) ;
726+ _builder . EmitOpCode ( ILOpCode . Dup ) ;
727+ _builder . EmitOpCode ( ILOpCode . Stsfld ) ;
728+ _builder . EmitToken ( cachingField , arrayCreation . Syntax , _diagnostics . DiagnosticBag ) ;
729+
730+ // arrayNotNullLabel:
731+ // new ReadOnlySpan<T>(array)
732+ _builder . MarkLabel ( arrayNotNullLabel ) ;
733+ _builder . EmitOpCode ( ILOpCode . Newobj , 0 ) ;
734+ EmitSymbolToken ( rosArrayCtor . AsMember ( spanType ) , arrayCreation . Syntax , optArgList : null ) ;
735+
736+ return true ;
737+ }
738+
739+ // The span is empty. Optimize away the array. This works regardless of the size of the type.
740+ void emitEmptyReadonlySpan ( NamedTypeSymbol spanType , BoundExpression wrappedExpression , bool used , BoundExpression ? inPlaceTarget )
741+ {
742+ // If this is in-place initialization, call the default ctor.
743+ if ( inPlaceTarget is notnull )
744+ {
745+ EmitAddress ( inPlaceTarget , Binder . AddressKind . Writeable ) ;
746+ _builder . EmitOpCode ( ILOpCode . Initobj ) ;
747+ EmitSymbolToken ( spanType , wrappedExpression . Syntax ) ;
748+ if ( used )
749+ {
750+ EmitExpression ( inPlaceTarget , used : true ) ;
751+ }
752+ }
753+ else
754+ {
755+ // Otherwise, assign it to a default value / empty span.
756+ Debug . Assert ( used ) ;
757+ EmitDefaultValue ( spanType , used , wrappedExpression . Syntax ) ;
758+ }
759+ }
760+
761+ bool tryGetReadOnlySpanArrayCtor ( SyntaxNode syntax , [ NotNullWhen ( true ) ] out MethodSymbol ? rosArrayCtor )
762+ {
763+ rosArrayCtor = ( MethodSymbol ? ) Binder . GetWellKnownTypeMember ( _module . Compilation , WellKnownMember . System_ReadOnlySpan_T__ctor_Array , _diagnostics , syntax : syntax , isOptional : true ) ;
764+ if ( rosArrayCtor is null )
765+ {
766+ // The ReadOnlySpan<T>(T[] array) constructor we need is missing or something went wrong.
767+ return false ;
768+ }
769+
770+ Debug . Assert ( ! rosArrayCtor . HasUnsupportedMetadata ) ;
771+ return true ;
652772}
653- Debug . Assert ( ! rosArrayCtor . HasUnsupportedMetadata ) ;
654-
655- // If we're dealing with an array of enums, we need to handle the possibility that the data blob
656- // is the same for multiple enums all with the same underlying type, or even with the underlying type
657- // itself. This is addressed by always caching an array for the underlying type, and then relying on
658- // arrays being covariant between the underlying type and the enum type, so that it's safe to do:
659- // new ReadOnlySpan<EnumType>(arrayOfUnderlyingType);
660- // It's important to have a consistent type here, as otherwise the type of the caching field could
661- // end up changing non-deterministically based on which type for a given blob was encountered first.
662- // Also, even if we're not dealing with an enum, we still create a new array type that drops any
663- // annotations that may have initially been associated with the element type; this is similarly to
664- // ensure deterministic behavior.
665- arrayType = arrayType . WithElementType ( TypeWithAnnotations . Create ( elementType . EnumUnderlyingTypeOrSelf ( ) ) ) ;
666-
667- var cachingField = _builder . module . GetArrayCachingFieldForData ( data , _module . Translate ( arrayType ) , wrappedExpression . Syntax , _diagnostics . DiagnosticBag ) ;
668- var arrayNotNullLabel = new object ( ) ;
669-
670- // T[]? array = PrivateImplementationDetails.cachingField;
671- // if (array is not null) goto arrayNotNull;
672- _builder . EmitOpCode ( ILOpCode . Ldsfld ) ;
673- _builder . EmitToken ( cachingField , wrappedExpression . Syntax , _diagnostics . DiagnosticBag ) ;
674- _builder . EmitOpCode ( ILOpCode . Dup ) ;
675- _builder . EmitBranch ( ILOpCode . Brtrue , arrayNotNullLabel ) ;
676-
677- // array = new T[elementCount];
678- // RuntimeHelpers.InitializeArray(token, array);
679- // PrivateImplementationDetails.cachingField = array;
680- _builder . EmitOpCode ( ILOpCode . Pop ) ;
681- _builder . EmitIntConstant ( elementCount ) ;
682- _builder . EmitOpCode ( ILOpCode . Newarr ) ;
683- EmitSymbolToken ( arrayType . ElementType , wrappedExpression . Syntax ) ;
684- _builder . EmitArrayBlockInitializer ( data , wrappedExpression . Syntax , _diagnostics . DiagnosticBag ) ;
685- _builder . EmitOpCode ( ILOpCode . Dup ) ;
686- _builder . EmitOpCode ( ILOpCode . Stsfld ) ;
687- _builder . EmitToken ( cachingField , wrappedExpression . Syntax , _diagnostics . DiagnosticBag ) ;
688-
689- // arrayNotNullLabel:
690- // new ReadOnlySpan<T>(array)
691- _builder . MarkLabel ( arrayNotNullLabel ) ;
692- _builder . EmitOpCode ( ILOpCode . Newobj , 0 ) ;
693- EmitSymbolToken ( rosArrayCtor . AsMember ( spanType ) , wrappedExpression . Syntax , optArgList : null ) ;
694- return true ;
695773}
696774
697775/// <summary>Gets whether the element type of an array is appropriate for storing in a blob.</summary>