@@ -316,6 +316,10 @@ private void EmitExpressionCore(BoundExpression expression, bool used)
316316 EmitComplexConditionalReceiver ( ( BoundComplexConditionalReceiver ) expression , used ) ;
317317 break ;
318318
319+ case BoundKind . ComplexReceiver :
320+ EmitComplexReceiver ( ( BoundComplexReceiver ) expression , used ) ;
321+ break ;
322+
319323 case BoundKind . PseudoVariable :
320324 EmitPseudoVariableValue ( ( BoundPseudoVariable ) expression , used ) ;
321325 break ;
@@ -365,7 +369,11 @@ private void EmitComplexConditionalReceiver(BoundComplexConditionalReceiver expr
365369
366370 EmitExpression ( expression . ReferenceTypeReceiver , used ) ;
367371 _builder . EmitBranch ( ILOpCode . Br , doneLabel ) ;
368- _builder . AdjustStack ( - 1 ) ;
372+
373+ if ( used )
374+ {
375+ _builder . AdjustStack ( - 1 ) ;
376+ }
369377
370378 _builder . MarkLabel ( whenValueTypeLabel ) ;
371379 EmitExpression ( expression . ValueTypeReceiver , used ) ;
@@ -412,7 +420,8 @@ private void EmitLoweredConditionalAccessExpression(BoundLoweredConditionalAcces
412420 ( ( TypeParameterSymbol ) receiverType ) . EffectiveInterfacesNoUseSiteDiagnostics . IsEmpty ) || // This could be a nullable value type, which must be copied in order to not mutate the original value
413421 LocalRewriter . CanChangeValueBetweenReads ( receiver , localsMayBeAssignedOrCaptured : false ) ||
414422 ( receiverType . IsReferenceType && receiverType . TypeKind == TypeKind . TypeParameter ) ||
415- ( receiver . Kind == BoundKind . Local && IsStackLocal ( ( ( BoundLocal ) receiver ) . LocalSymbol ) ) ;
423+ ( receiver . Kind == BoundKind . Local && IsStackLocal ( ( ( BoundLocal ) receiver ) . LocalSymbol ) ) ||
424+ ( notConstrained && IsConditionalConstrainedCallThatMustUseTempForReferenceTypeReceiverWalker . Analyze ( expression ) ) ;
416425
417426 // ===== RECEIVER
418427 if ( nullCheckOnCopy )
@@ -564,6 +573,60 @@ private void EmitLoweredConditionalAccessExpression(BoundLoweredConditionalAcces
564573 }
565574 }
566575
576+ private sealed class IsConditionalConstrainedCallThatMustUseTempForReferenceTypeReceiverWalker : BoundTreeWalkerWithStackGuardWithoutRecursionOnTheLeftOfBinaryOperator
577+ {
578+ private readonly BoundLoweredConditionalAccess _conditionalAccess ;
579+ private bool ? _result ;
580+
581+ private IsConditionalConstrainedCallThatMustUseTempForReferenceTypeReceiverWalker ( BoundLoweredConditionalAccess conditionalAccess )
582+ : base ( )
583+ {
584+ _conditionalAccess = conditionalAccess ;
585+ }
586+
587+ public static bool Analyze ( BoundLoweredConditionalAccess conditionalAccess )
588+ {
589+ var walker = new IsConditionalConstrainedCallThatMustUseTempForReferenceTypeReceiverWalker ( conditionalAccess ) ;
590+ walker . Visit ( conditionalAccess . WhenNotNull ) ;
591+ Debug . Assert ( walker . _result . HasValue ) ;
592+ return walker . _result . GetValueOrDefault ( ) ;
593+ }
594+
595+ public override BoundNode Visit ( BoundNode node )
596+ {
597+ if ( _result . HasValue )
598+ {
599+ return null ;
600+ }
601+
602+ return base . Visit ( node ) ;
603+ }
604+
605+ public override BoundNode VisitCall ( BoundCall node )
606+ {
607+ if ( node . ReceiverOpt is BoundConditionalReceiver { Id : var id } && id == _conditionalAccess . Id )
608+ {
609+ Debug . Assert ( ! _result . HasValue ) ;
610+ _result = ! IsSafeToDereferenceReceiverRefAfterEvaluatingArguments ( node . Arguments ) ;
611+ return null ;
612+ }
613+
614+ return base . VisitCall ( node ) ;
615+ }
616+
617+ public override BoundNode VisitConditionalReceiver ( BoundConditionalReceiver node )
618+ {
619+ if ( node . Id == _conditionalAccess . Id )
620+ {
621+ Debug . Assert ( ! _result . HasValue ) ;
622+ _result = false ;
623+ return null ;
624+ }
625+
626+ return base . VisitConditionalReceiver ( node ) ;
627+ }
628+ }
629+
567630 private void EmitConditionalReceiver ( BoundConditionalReceiver expression , bool used )
568631 {
569632 Debug . Assert ( ! expression . Type . IsValueType ) ;
@@ -1752,25 +1815,21 @@ LocalDefinition emitGenericReceiver(BoundCall call, out CallKind callKind)
17521815 // reference to the stack. So, for a class we need to emit a reference to a temporary
17531816 // location, rather than to the original location
17541817
1755- // Struct values are never nulls.
1756- // We will emit a check for such case, but the check is really a JIT-time
1818+ // We will emit a runtime check for a class, but the check is really a JIT-time
17571819 // constant since JIT will know if T is a struct or not.
17581820
1759- // if ((object)default(T) == null)
1821+ // if (!typeof(T).IsValueType)
17601822 // {
17611823 // temp = receiverRef
17621824 // receiverRef = ref temp
17631825 // }
17641826
1765- object whenNotNullLabel = null ;
1827+ object whenValueTypeLabel = null ;
17661828
17671829 if ( ! receiverType . IsReferenceType )
17681830 {
1769- // if ((object)default(T) == null)
1770- EmitDefaultValue ( receiverType , true , receiver . Syntax ) ;
1771- EmitBox ( receiverType , receiver . Syntax ) ;
1772- whenNotNullLabel = new object ( ) ;
1773- _builder . EmitBranch ( ILOpCode . Brtrue , whenNotNullLabel ) ;
1831+ // if (!typeof(T).IsValueType)
1832+ whenValueTypeLabel = TryEmitIsValueTypeBranch ( receiverType , receiver . Syntax ) ;
17741833 }
17751834
17761835 // temp = receiverRef
@@ -1780,16 +1839,91 @@ LocalDefinition emitGenericReceiver(BoundCall call, out CallKind callKind)
17801839 _builder . EmitLocalStore ( tempOpt ) ;
17811840 _builder . EmitLocalAddress ( tempOpt ) ;
17821841
1783- if ( whenNotNullLabel is not null )
1842+ if ( whenValueTypeLabel is not null )
17841843 {
1785- _builder . MarkLabel ( whenNotNullLabel ) ;
1844+ _builder . MarkLabel ( whenValueTypeLabel ) ;
17861845 }
17871846 }
17881847
17891848 return tempOpt ;
17901849 }
17911850 }
17921851
1852+ private object TryEmitIsValueTypeBranch ( TypeSymbol type , SyntaxNode syntax )
1853+ {
1854+ if ( Binder . GetWellKnownTypeMember ( _module . Compilation , WellKnownMember . System_Type__GetTypeFromHandle , _diagnostics , syntax : syntax , isOptional : false ) is MethodSymbol getTypeFromHandle &&
1855+ Binder . GetWellKnownTypeMember ( _module . Compilation , WellKnownMember . System_Type__get_IsValueType , _diagnostics , syntax : syntax , isOptional : false ) is MethodSymbol getIsValueType )
1856+ {
1857+ _builder . EmitOpCode ( ILOpCode . Ldtoken ) ;
1858+ EmitSymbolToken ( type , syntax ) ;
1859+ _builder . EmitOpCode ( ILOpCode . Call , stackAdjustment : 0 ) ; // argument off, return value on
1860+ EmitSymbolToken ( getTypeFromHandle , syntax , null ) ;
1861+ _builder . EmitOpCode ( ILOpCode . Call , stackAdjustment : 0 ) ; // instance off, return value on
1862+ EmitSymbolToken ( getIsValueType , syntax , null ) ;
1863+ var whenValueTypeLabel = new object ( ) ;
1864+ _builder . EmitBranch ( ILOpCode . Brtrue , whenValueTypeLabel ) ;
1865+
1866+ return whenValueTypeLabel ;
1867+ }
1868+
1869+ return null ;
1870+ }
1871+
1872+ private void EmitComplexReceiver ( BoundComplexReceiver expression , bool used )
1873+ {
1874+ Debug . Assert ( ! used ) ;
1875+ Debug . Assert ( ! expression . Type . IsReferenceType ) ;
1876+ Debug . Assert ( ! expression . Type . IsValueType ) ;
1877+
1878+ var receiverType = expression . Type ;
1879+
1880+ // if (!typeof(T).IsValueType)
1881+ object whenValueTypeLabel = TryEmitIsValueTypeBranch ( receiverType , expression . Syntax ) ;
1882+
1883+ EmitExpression ( expression . ReferenceTypeReceiver , used ) ;
1884+ var doneLabel = new object ( ) ;
1885+ _builder . EmitBranch ( ILOpCode . Br , doneLabel ) ;
1886+
1887+ if ( whenValueTypeLabel is not null )
1888+ {
1889+ if ( used )
1890+ {
1891+ _builder . AdjustStack ( - 1 ) ;
1892+ }
1893+
1894+ _builder . MarkLabel ( whenValueTypeLabel ) ;
1895+ EmitExpression ( expression . ValueTypeReceiver , used ) ;
1896+ }
1897+
1898+ _builder . MarkLabel ( doneLabel ) ;
1899+ }
1900+
1901+ private void EmitComplexReceiverAddress ( BoundComplexReceiver expression )
1902+ {
1903+ Debug . Assert ( ! expression . Type . IsReferenceType ) ;
1904+ Debug . Assert ( ! expression . Type . IsValueType ) ;
1905+
1906+ var receiverType = expression . Type ;
1907+
1908+ // if (!typeof(T).IsValueType)
1909+ object whenValueTypeLabel = TryEmitIsValueTypeBranch ( receiverType , expression . Syntax ) ;
1910+
1911+ var receiverTemp = EmitAddress ( expression . ReferenceTypeReceiver , AddressKind . ReadOnly ) ;
1912+ Debug . Assert ( receiverTemp == null ) ;
1913+ var doneLabel = new Object ( ) ;
1914+ _builder . EmitBranch ( ILOpCode . Br , doneLabel ) ;
1915+
1916+ if ( whenValueTypeLabel is not null )
1917+ {
1918+ _builder . AdjustStack ( - 1 ) ;
1919+ _builder . MarkLabel ( whenValueTypeLabel ) ;
1920+ // we will not write through this receiver, but it could be a target of mutating calls
1921+ EmitReceiverRef ( expression . ValueTypeReceiver , AddressKind . Constrained ) ;
1922+ }
1923+
1924+ _builder . MarkLabel ( doneLabel ) ;
1925+ }
1926+
17931927 internal static bool IsPossibleReferenceTypeReceiverOfConstrainedCall ( BoundExpression receiver )
17941928 {
17951929 var receiverType = receiver . Type ;
@@ -1811,7 +1945,9 @@ internal static bool ReceiverIsKnownToReferToTempIfReferenceType(BoundExpression
18111945
18121946 if ( receiver is
18131947 BoundLocal { LocalSymbol . IsKnownToReferToTempIfReferenceType : true } or
1814- BoundComplexConditionalReceiver )
1948+ BoundComplexConditionalReceiver or
1949+ BoundComplexReceiver or
1950+ BoundConditionalReceiver { Type : { IsReferenceType : false , IsValueType : false } } )
18151951 {
18161952 return true ;
18171953 }
0 commit comments