/
githubmirror
/
roslyn
Обзор
Документация
Войти
/
githubmirror
/
roslyn
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/Compilers/VisualBasic/Portable/CodeGen/EmitAddress.vb
520 строк
23 KB
Tomáš Matoušek
Add support for FieldRva to EnC delta (#78033)
10 май 2025, 20:30
Не верифицирован
10 май 2025, 20:30
7c62502
Код
Авторство
О чём код?
' 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. Imports System.Reflection.Metadata Imports Microsoft.CodeAnalysis.CodeGen Imports Microsoft.CodeAnalysis.VisualBasic.Symbols Namespace Microsoft.CodeAnalysis.VisualBasic.CodeGen Partial Friend Class CodeGenerator ' VB has additional, stronger than CLR requirements on whether a reference to an item ' can be taken. ' ' In particular in VB read-only requirements are recursive. ' For example struct fields of readonly local are also considered readonly. ' If one needs to take a reference of such local it is not enough to not write to the ' reference itself. We need to guarantee that through such reference ' nested fields will not be changed too. Otherwise a clone must be created. ' ' On the other hand CLR only cares about shallow readonlyness. ' ' To have enough information in both cases we use 3-state value (unlike C# that just uses bool) ' That specifies the context under which a reference is taken. Private Enum AddressKind ' reference may be written to Writeable ' reference itself will not be written to, but may be used to modify fields. [ReadOnly] ' will not directly or indirectly assign to the reference or to fields. Immutable End Enum ''' <summary> ''' Emits address as in & ''' ''' May introduce a temp which it will return. (otherwise returns null) ''' </summary> Private Function EmitAddress(expression As BoundExpression, addressKind As AddressKind) As LocalDefinition Dim kind As BoundKind = expression.Kind Dim tempOpt As LocalDefinition = Nothing Dim allowedToTakeReference As Boolean = AllowedToTakeRef(expression, addressKind) If Not allowedToTakeReference Then ' language says expression should not be mutated. Emit address of a clone. Return EmitAddressOfTempClone(expression) End If Select Case kind Case BoundKind.Local Dim boundLocal = DirectCast(expression, BoundLocal) If IsStackLocal(boundLocal.LocalSymbol) Then Debug.Assert(boundLocal.LocalSymbol.IsByRef) ' only allow byref locals in this context ' do nothing, it should be on the stack Else Dim local = GetLocal(boundLocal) _builder.EmitLocalAddress(local) ' EmitLocalAddress knows about byref locals End If Case BoundKind.Dup Debug.Assert(DirectCast(expression, BoundDup).IsReference, "taking address of a stack value?") _builder.EmitOpCode(ILOpCode.Dup) Case BoundKind.ReferenceAssignment EmitReferenceAssignment(DirectCast(expression, BoundReferenceAssignment), used:=True, needReference:=True) Case BoundKind.ConditionalAccessReceiverPlaceholder ' do nothing receiver ref must be already pushed Debug.Assert(Not expression.Type.IsReferenceType) Debug.Assert(Not expression.Type.IsValueType OrElse expression.Type.IsNullableType()) Case BoundKind.ComplexConditionalAccessReceiver EmitComplexConditionalAccessReceiverAddress(DirectCast(expression, BoundComplexConditionalAccessReceiver)) Case BoundKind.Parameter EmitParameterAddress(DirectCast(expression, BoundParameter)) Case BoundKind.FieldAccess tempOpt = EmitFieldAddress(DirectCast(expression, BoundFieldAccess), addressKind) Case BoundKind.ArrayAccess EmitArrayElementAddress(DirectCast(expression, BoundArrayAccess), addressKind) Case BoundKind.MeReference, BoundKind.MyClassReference Debug.Assert(expression.Type.IsValueType, "only valuetypes may need a ref to Me/MyClass") _builder.EmitOpCode(ILOpCode.Ldarg_0) Case BoundKind.ValueTypeMeReference _builder.EmitOpCode(ILOpCode.Ldarg_0) Case BoundKind.MyBaseReference Debug.Assert(False, "base is always a reference type, why one may need a reference to it?") Case BoundKind.Parenthesized ' rewriter should take care of Parenthesized ' ' we do not know how to emit address of a parenthesized without context. ' when it is an argument like goo((arg)), it must be cloned, ' in other cases like (receiver).goo() it might not need to be... ' Debug.Assert(False, "we should not see parenthesized in EmitAddress.") Case BoundKind.Sequence tempOpt = EmitSequenceAddress(DirectCast(expression, BoundSequence), addressKind) Case BoundKind.SequencePointExpression EmitSequencePointExpressionAddress(DirectCast(expression, BoundSequencePointExpression), addressKind) Case BoundKind.PseudoVariable EmitPseudoVariableAddress(DirectCast(expression, BoundPseudoVariable)) Case BoundKind.Call Dim [call] = DirectCast(expression, BoundCall) Debug.Assert([call].Method.ReturnsByRef) EmitCallExpression([call], UseKind.UsedAsAddress) Case Else Throw ExceptionUtilities.UnexpectedValue(kind) End Select Return tempOpt End Function Private Sub EmitPseudoVariableAddress(expression As BoundPseudoVariable) EmitExpression(expression.EmitExpressions.GetAddress(expression), used:=True) End Sub ''' <summary> ''' Emits address of a temp. ''' Used in cases where taking address directly is not possible ''' (typically because expression does not have a home) ''' ''' Will introduce a temp which it will return. ''' </summary> Private Function EmitAddressOfTempClone(expression As BoundExpression) As LocalDefinition EmitExpression(expression, True) Dim value = AllocateTemp(expression.Type, expression.Syntax) _builder.EmitLocalStore(value) _builder.EmitLocalAddress(value) Return value End Function Private Function EmitSequenceAddress(sequence As BoundSequence, addressKind As AddressKind) As LocalDefinition Dim hasLocals As Boolean = Not sequence.Locals.IsEmpty If hasLocals Then _builder.OpenLocalScope() For Each local In sequence.Locals Me.DefineLocal(local, sequence.Syntax) Next End If Me.EmitSideEffects(sequence.SideEffects) Debug.Assert(sequence.ValueOpt IsNot Nothing) Dim tempOpt = Me.EmitAddress(sequence.ValueOpt, addressKind) ' when a sequence Is happened to be a byref receiver ' we may need to extend the life time of the target until we are done accessing it ' {.v ; v = Goo(); v}.Bar() // v should be released after Bar() Is over. Dim doNotRelease As LocalSymbol = Nothing If (tempOpt Is Nothing) Then Dim referencedLocal As BoundLocal = DigForLocal(sequence.ValueOpt) If (referencedLocal IsNot Nothing) Then doNotRelease = referencedLocal.LocalSymbol End If End If If hasLocals Then _builder.CloseLocalScope() For Each local In sequence.Locals If (local IsNot doNotRelease) Then FreeLocal(local) Else tempOpt = GetLocal(doNotRelease) End If Next End If Return tempOpt End Function Private Function DigForLocal(value As BoundExpression) As BoundLocal Select Case value.Kind Case BoundKind.Local Dim local = DirectCast(value, BoundLocal) If Not local.LocalSymbol.IsByRef Then Return local End If Case BoundKind.Sequence Return DigForLocal((DirectCast(value, BoundSequence)).ValueOpt) Case BoundKind.FieldAccess Dim fieldAccess = DirectCast(value, BoundFieldAccess) If Not fieldAccess.FieldSymbol.IsShared Then Return DigForLocal(fieldAccess.ReceiverOpt) End If End Select Return Nothing End Function ''' <summary> ''' Checks if expression represents directly or indirectly a value with its own home. ''' In such cases it is possible to get a reference without loading into a temporary. ''' ''' This is a CLR concept which is weaker than VB's IsLValue. ''' For example all locals are homed even if VB may consider some locals read-only. ''' </summary> Private Function HasHome(expression As BoundExpression) As Boolean Select Case expression.Kind Case BoundKind.Sequence Dim boundSequenceValue = DirectCast(expression, BoundSequence).ValueOpt Return boundSequenceValue IsNot Nothing AndAlso Me.HasHome(boundSequenceValue) Case BoundKind.FieldAccess Return HasHome(DirectCast(expression, BoundFieldAccess)) Case BoundKind.MeReference, BoundKind.MyBaseReference, BoundKind.ArrayAccess, BoundKind.ReferenceAssignment, BoundKind.Parameter Return True Case BoundKind.Local 'Note: in a case if we have a reference in a stack local, we definitely can "obtain" a reference. ' Unlike a case where we have a value. Dim local = DirectCast(expression, BoundLocal).LocalSymbol Return Not IsStackLocal(local) OrElse local.IsByRef Case BoundKind.Call Dim method = DirectCast(expression, BoundCall).Method Return method.ReturnsByRef Case BoundKind.Dup ' For a dupped local we assume that if the dup ' is created for byref local it does have home Return DirectCast(expression, BoundDup).IsReference Case BoundKind.ValueTypeMeReference Return True End Select Return False End Function ''' <summary> ''' Special HasHome for fields. Fields have homes when they are writable. ''' </summary> Private Function HasHome(fieldAccess As BoundFieldAccess) As Boolean Dim field = fieldAccess.FieldSymbol ' const fields are literal values with no homes If field.IsConst AndAlso Not field.IsConstButNotMetadataConstant Then Return False End If If Not field.IsReadOnly Then Return True End If ' while readonly fields have home it is not valid to refer to it when not constructing. If Not TypeSymbol.Equals(field.ContainingType, Me._method.ContainingType, TypeCompareKind.ConsiderEverything) Then Return False End If If field.IsShared Then Return Me._method.MethodKind = MethodKind.SharedConstructor Else Return Me._method.MethodKind = MethodKind.Constructor AndAlso fieldAccess.ReceiverOpt.Kind = BoundKind.MeReference End If End Function ''' <summary> ''' Checks if it is allowed to take a writable reference to expression according to VB rules. ''' </summary> Private Function AllowedToTakeRef(expression As BoundExpression, addressKind As AddressKind) As Boolean If expression.Kind = BoundKind.ConditionalAccessReceiverPlaceholder OrElse expression.Kind = BoundKind.ComplexConditionalAccessReceiver Then Return addressKind = AddressKind.ReadOnly OrElse addressKind = AddressKind.Immutable Or expression.Type.IsNullableType() End If ' taking immutable addresses is ok as long as expression has home If addressKind <> AddressKind.Immutable Then Select Case expression.Kind Case BoundKind.Sequence Dim boundSequenceValue = DirectCast(expression, BoundSequence).ValueOpt Return boundSequenceValue IsNot Nothing AndAlso Me.AllowedToTakeRef(boundSequenceValue, addressKind) Case BoundKind.FieldAccess Return AllowedToTakeRef(DirectCast(expression, BoundFieldAccess), addressKind) Case BoundKind.Local ' VB has concept of readonly locals. Some constants look like locals too. Return AllowedToTakeRef(DirectCast(expression, BoundLocal), addressKind) Case BoundKind.Parameter ' parameters can be readonly (parameters in query lambdas) ' we ignore their read-onlyness for Dev10 compatibility Return True Case BoundKind.PseudoVariable Return True Case BoundKind.Dup ' If this is a Dup of ByRef local we can use the address directly Return DirectCast(expression, BoundDup).IsReference Case BoundKind.MeReference, BoundKind.MyClassReference ' cannot modify Me/MyClass wholesale, but can modify fields ' from within structure methods. Return addressKind <> CodeGenerator.AddressKind.Writeable End Select End If Return HasHome(expression) End Function ''' <summary> ''' Checks if it is allowed to take a writable reference to expression according to VB rules. ''' </summary> Private Function AllowedToTakeRef(boundLocal As BoundLocal, addressKind As AddressKind) As Boolean Debug.Assert(addressKind <> CodeGenerator.AddressKind.Immutable, "immutable address is always ok") ' TODO: The condition is applicable only when LocalSymbol.IsReadOnly ' cannot write to a readonly local unless explicitly marked as an LValue If addressKind = CodeGenerator.AddressKind.Writeable AndAlso boundLocal.LocalSymbol.IsReadOnly AndAlso Not boundLocal.IsLValue Then Return False End If ' cannot take address of homeless local If Not HasHome(boundLocal) Then Return False End If 'TODO: we have to do the following to separate real locals ' from constants. ' ideally we should not see local constants at this point, ' they all should be either true locals (decimal, datetime) or BoundLiterals. If boundLocal.IsConstant Then Dim localConstType = boundLocal.Type If Not localConstType.IsDecimalType AndAlso Not localConstType.IsDateTimeType Then ' this is not a local. It is a named literal. Return False End If End If Return True End Function ''' <summary> ''' Can take a reference. ''' </summary> Private Function AllowedToTakeRef(fieldAccess As BoundFieldAccess, addressKind As AddressKind) As Boolean ' taking immutable addresses is ok as long as expression has home If addressKind <> AddressKind.Immutable Then ' If this field itself does not have home it cannot be mutated If Not HasHome(fieldAccess) Then Return False End If ' fields of readonly structs are considered recursively readonly in VB If fieldAccess.FieldSymbol.ContainingType.IsValueType Then Dim fieldReceiver = fieldAccess.ReceiverOpt ' even when writing to a field, receiver is accessed as readonly If fieldReceiver IsNot Nothing AndAlso Not AllowedToTakeRef(fieldReceiver, CodeGenerator.AddressKind.ReadOnly) Then If Not HasHome(fieldReceiver) Then ' can mutate field since its parent has no home and we will be dealing with a copy Return True Else ' this field access is readonly due to language reasons - ' most likely topmost receiver is a readonly local or a runtime const Return False End If End If End If End If Return HasHome(fieldAccess) End Function Private Sub EmitArrayElementAddress(arrayAccess As BoundArrayAccess, addressKind As AddressKind) EmitExpression(arrayAccess.Expression, True) EmitExpressions(arrayAccess.Indices, True) Dim elementType As TypeSymbol = arrayAccess.Type 'arrays are covariant, but elements can be written to. 'the flag tells that we do not intend to use the address for writing. If addressKind <> AddressKind.Writeable AndAlso elementType.IsTypeParameter() Then _builder.EmitOpCode(ILOpCode.Readonly) End If If DirectCast(arrayAccess.Expression.Type, ArrayTypeSymbol).IsSZArray Then _builder.EmitOpCode(ILOpCode.Ldelema) EmitSymbolToken(elementType, arrayAccess.Syntax) Else _builder.EmitArrayElementAddress(_module.Translate(DirectCast(arrayAccess.Expression.Type, ArrayTypeSymbol)), arrayAccess.Syntax) End If End Sub Private Function EmitFieldAddress(fieldAccess As BoundFieldAccess, addressKind As AddressKind) As LocalDefinition Dim field = fieldAccess.FieldSymbol If fieldAccess.FieldSymbol.IsShared Then EmitStaticFieldAddress(field, fieldAccess.Syntax) Return Nothing Else Return EmitInstanceFieldAddress(fieldAccess, addressKind) End If End Function Private Sub EmitStaticFieldAddress(field As FieldSymbol, syntaxNode As SyntaxNode) _builder.EmitOpCode(ILOpCode.Ldsflda) EmitSymbolToken(field, syntaxNode) End Sub Private Sub EmitParameterAddress(parameter As BoundParameter) Dim slot As Integer = ParameterSlot(parameter) If Not parameter.ParameterSymbol.IsByRef Then _builder.EmitLoadArgumentAddrOpcode(slot) Else _builder.EmitLoadArgumentOpcode(slot) End If End Sub ''' <summary> ''' Emits receiver in a form that allows member accesses ( O or & ). For verifiably ''' reference types it is the actual reference. For generic types it is a address of the ''' receiver with readonly intent. For the value types it is an address of the receiver. ''' ''' isAccessConstrained indicates that receiver is a target of a constrained callvirt ''' in such case it is unnecessary to box a receiver that is typed to a type parameter ''' ''' May introduce a temp which it will return. (otherwise returns null) ''' </summary> Private Function EmitReceiverRef(receiver As BoundExpression, isAccessConstrained As Boolean, addressKind As AddressKind) As LocalDefinition Dim receiverType = receiver.Type If IsVerifierReference(receiverType) Then EmitExpression(receiver, used:=True) Return Nothing End If If receiverType.TypeKind = TypeKind.TypeParameter Then '[Note: Constraints on a generic parameter only restrict the types that 'the generic parameter may be instantiated with. Verification (see Partition III) 'requires that a field, property or method that a generic parameter is known 'to provide through meeting a constraint, cannot be directly accessed/called 'via the generic parameter unless it is first boxed (see Partition III) or 'the callvirt instruction is prefixed with the constrained. prefix instruction '(see Partition III). end note] If isAccessConstrained Then Return EmitAddress(receiver, AddressKind.ReadOnly) Else EmitExpression(receiver, used:=True) ' conditional receivers are already boxed if needed when pushed If receiver.Kind <> BoundKind.ConditionalAccessReceiverPlaceholder Then EmitBox(receiverType, receiver.Syntax) End If Return Nothing End If End If Debug.Assert(IsVerifierValue(receiverType)) Return EmitAddress(receiver, addressKind) End Function ''' <summary> ''' May introduce a temp which it will return. (otherwise returns null) ''' </summary> Private Function EmitInstanceFieldAddress(fieldAccess As BoundFieldAccess, addressKind As AddressKind) As LocalDefinition Dim field = fieldAccess.FieldSymbol ' writing to a field is considered reading a receiver, unless receiver is a struct and not "Me" If addressKind = AddressKind.Writeable AndAlso IsMeReceiver(fieldAccess.ReceiverOpt) Then addressKind = AddressKind.ReadOnly End If Dim tempOpt = EmitReceiverRef(fieldAccess.ReceiverOpt, isAccessConstrained:=False, addressKind:=addressKind) Debug.Assert(HasHome(fieldAccess), "taking a ref of homeless field") _builder.EmitOpCode(ILOpCode.Ldflda) EmitSymbolToken(field, fieldAccess.Syntax) Return tempOpt End Function End Class End Namespace