/
githubmirror
/
roslyn
Обзор
Документация
Войти
/
githubmirror
/
roslyn
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/Compilers/CSharp/Portable/CodeGen/EmitConversion.cs
429 строк
19 KB
AlekseyTs
SourceMemberMethodSymbol.IsMetadataVirtual should force complete declaring type when queried by a different module (#84013)
05 июн 2026, 17:03
Не верифицирован
05 июн 2026, 17:03
04c548a
Код
Авторство
О чём код?
// 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.Diagnostics; using System.Reflection.Metadata; using Microsoft.CodeAnalysis.CSharp.Symbols; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CSharp.CodeGen { internal partial class CodeGenerator { private static bool IsNumeric(TypeSymbol type) { switch (type.PrimitiveTypeCode) { case Cci.PrimitiveTypeCode.Int8: case Cci.PrimitiveTypeCode.UInt8: case Cci.PrimitiveTypeCode.Int16: case Cci.PrimitiveTypeCode.UInt16: case Cci.PrimitiveTypeCode.Int32: case Cci.PrimitiveTypeCode.UInt32: case Cci.PrimitiveTypeCode.Int64: case Cci.PrimitiveTypeCode.UInt64: case Cci.PrimitiveTypeCode.Char: case Cci.PrimitiveTypeCode.Float32: case Cci.PrimitiveTypeCode.Float64: return true; case Cci.PrimitiveTypeCode.IntPtr: case Cci.PrimitiveTypeCode.UIntPtr: return type.IsNativeIntegerType; default: return false; } } private void EmitConversionExpression(BoundConversion conversion, bool used) { switch (conversion.ConversionKind) { case ConversionKind.MethodGroup: throw ExceptionUtilities.UnexpectedValue(conversion.ConversionKind); case ConversionKind.ImplicitNullToPointer: // The null pointer is represented as 0u. _builder.EmitIntConstant(0); _builder.EmitOpCode(ILOpCode.Conv_u); EmitPopIfUnused(used); return; } var operand = conversion.Operand; if (!used && !conversion.ConversionHasSideEffects()) { EmitExpression(operand, false); // just do expr side effects return; } EmitExpression(operand, true); EmitConversion(conversion); EmitPopIfUnused(used); } private void EmitReadOnlySpanFromArrayExpression(BoundReadOnlySpanFromArray expression, bool used) { BoundExpression operand = expression.Operand; var typeTo = (NamedTypeSymbol)expression.Type; Debug.Assert((operand.Type.IsArray()) && this._module.Compilation.IsReadOnlySpanType(typeTo), "only special kinds of conversions involving ReadOnlySpan may be handled in emit"); if (!TryEmitOptimizedReadonlySpanCreation(typeTo, operand, used, inPlaceTarget: null, avoidInPlace: out _)) { // there are several reasons that could prevent us from emitting a wrapper // in such case we just emit the operand and then invoke the conversion method EmitExpression(operand, used); if (used) { // consumes 1 argument (array) and produces one result (span) _builder.EmitOpCode(ILOpCode.Call, stackAdjustment: 0); EmitSymbolToken(expression.ConversionMethod, expression.Syntax, optArgList: null); } } } private void EmitConversion(BoundConversion conversion) { AssertIsEmitConversionKind(conversion.ConversionKind); switch (conversion.ConversionKind) { case ConversionKind.Identity: EmitIdentityConversion(conversion); break; case ConversionKind.ImplicitNumeric: case ConversionKind.ExplicitNumeric: EmitNumericConversion(conversion); break; case ConversionKind.ImplicitReference: case ConversionKind.Boxing: // from IL perspective ImplicitReference and Boxing conversions are the same thing. // both force operand to be an object (O) - which may involve boxing // and then assume that result has the target type - which may involve unboxing. EmitImplicitReferenceConversion(conversion); break; case ConversionKind.ExplicitReference: case ConversionKind.Unboxing: // from IL perspective ExplicitReference and UnBoxing conversions are the same thing. // both force operand to be an object (O) - which may involve boxing // and then reinterpret result as the target type - which may involve unboxing. EmitExplicitReferenceConversion(conversion); break; case ConversionKind.ImplicitEnumeration: case ConversionKind.ExplicitEnumeration: EmitEnumConversion(conversion); break; case ConversionKind.ImplicitUserDefined: case ConversionKind.ExplicitUserDefined: case ConversionKind.Union: case ConversionKind.AnonymousFunction: case ConversionKind.MethodGroup: case ConversionKind.ImplicitTupleLiteral: case ConversionKind.ImplicitTuple: case ConversionKind.ExplicitTupleLiteral: case ConversionKind.ExplicitTuple: case ConversionKind.ImplicitDynamic: case ConversionKind.ExplicitDynamic: case ConversionKind.ImplicitThrow: // None of these things should reach codegen (yet? maybe?) throw ExceptionUtilities.UnexpectedValue(conversion.ConversionKind); case ConversionKind.ImplicitPointerToVoid: case ConversionKind.ExplicitPointerToPointer: case ConversionKind.ImplicitPointer: return; //no-op since they all have the same runtime representation case ConversionKind.ExplicitPointerToInteger: case ConversionKind.ExplicitIntegerToPointer: var fromType = conversion.Operand.Type; var fromPredefTypeKind = fromType.PrimitiveTypeCode; var toType = conversion.Type; var toPredefTypeKind = toType.PrimitiveTypeCode; #if DEBUG switch (fromPredefTypeKind) { case Microsoft.Cci.PrimitiveTypeCode.IntPtr when !fromType.IsNativeIntegerType: case Microsoft.Cci.PrimitiveTypeCode.UIntPtr when !fromType.IsNativeIntegerType: case Microsoft.Cci.PrimitiveTypeCode.Pointer: case Microsoft.Cci.PrimitiveTypeCode.FunctionPointer: Debug.Assert(IsNumeric(toType)); break; default: Debug.Assert(IsNumeric(fromType)); Debug.Assert( (toPredefTypeKind == Microsoft.Cci.PrimitiveTypeCode.IntPtr || toPredefTypeKind == Microsoft.Cci.PrimitiveTypeCode.UIntPtr) && !toType.IsNativeIntegerWrapperType || toPredefTypeKind == Microsoft.Cci.PrimitiveTypeCode.Pointer || toPredefTypeKind == Microsoft.Cci.PrimitiveTypeCode.FunctionPointer || (fromPredefTypeKind == Cci.PrimitiveTypeCode.IntPtr && conversion.Operand is BoundBinaryOperator { OperatorKind: BinaryOperatorKind.Division })); // pointer subtraction: see LocalRewriter.RewritePointerSubtraction() break; } #endif _builder.EmitNumericConversion(fromPredefTypeKind, toPredefTypeKind, conversion.Checked); break; case ConversionKind.PinnedObjectToPointer: // CLR allows unsafe conversion from(O) to native int/uint. // The conversion does not change the representation of the value, // but the value will not be reported to subsequent GC operations (and therefore will not be updated by such operations) _builder.EmitOpCode(ILOpCode.Conv_u); break; case ConversionKind.ImplicitNullToPointer: throw ExceptionUtilities.UnexpectedValue(conversion.ConversionKind); // Should be handled by caller. case ConversionKind.ImplicitNullable: case ConversionKind.ExplicitNullable: default: throw ExceptionUtilities.UnexpectedValue(conversion.ConversionKind); } } [Conditional("DEBUG")] internal static void AssertIsEmitConversionKind(ConversionKind conversionKind) { switch (conversionKind) { case ConversionKind.Identity: case ConversionKind.ImplicitNumeric: case ConversionKind.ExplicitNumeric: case ConversionKind.ImplicitReference: case ConversionKind.Boxing: case ConversionKind.ExplicitReference: case ConversionKind.Unboxing: case ConversionKind.ImplicitEnumeration: case ConversionKind.ExplicitEnumeration: case ConversionKind.ImplicitPointerToVoid: case ConversionKind.ExplicitPointerToPointer: case ConversionKind.ImplicitPointer: case ConversionKind.ExplicitPointerToInteger: case ConversionKind.ExplicitIntegerToPointer: case ConversionKind.PinnedObjectToPointer: case ConversionKind.ImplicitNullToPointer: break; default: ExceptionUtilities.UnexpectedValue(conversionKind); break; } } private void EmitIdentityConversion(BoundConversion conversion) { // An _explicit_ identity conversion from double to double or float to float on // non-constants must stay as a conversion. An _implicit_ identity conversion can be // optimized away. Why? Because (double)d1 + d2 has different semantics than d1 + d2. // The former rounds off to 64 bit precision; the latter is permitted to use higher // precision math if d1 is enregistered. if (conversion.ExplicitCastInCode) { switch (conversion.Type.PrimitiveTypeCode) { case Microsoft.Cci.PrimitiveTypeCode.Float32: case Microsoft.Cci.PrimitiveTypeCode.Float64: // For explicitly-written "identity conversions" from float to float or // double to double, we require the generation of conv.r4 or conv.r8. The // runtime can use these instructions to truncate precision, and csc.exe // generates them. It's not ideal, we should consider the possibility of not // doing this or marking somewhere else that this is necessary. // Don't need to do this for constants, however. if (conversion.Operand.ConstantValueOpt == null) { EmitNumericConversion(conversion); } break; } } } private void EmitNumericConversion(BoundConversion conversion) { var fromType = conversion.Operand.Type; var fromPredefTypeKind = fromType.PrimitiveTypeCode; Debug.Assert(IsNumeric(fromType)); var toType = conversion.Type; var toPredefTypeKind = toType.PrimitiveTypeCode; Debug.Assert(IsNumeric(toType)); _builder.EmitNumericConversion(fromPredefTypeKind, toPredefTypeKind, conversion.Checked); } private void EmitImplicitReferenceConversion(BoundConversion conversion) { // turn operand into an O(operandType) // if the operand is already verifiably an O, we can use it as-is // otherwise we need to box it, so that verifier will start tracking an O if (!conversion.Operand.Type.IsVerifierReference()) { EmitBox(conversion.Operand.Type, conversion.Operand.Syntax); } // here we have O(operandType) that must be compatible with O(targetType) // // if target type is verifiably a reference type, we can leave the value as-is otherwise // we need to unbox to targetType to keep verifier happy. var resultType = conversion.Type; if (!resultType.IsVerifierReference()) { _builder.EmitOpCode(ILOpCode.Unbox_any); EmitSymbolToken(conversion.Type, conversion.Syntax); } else if (resultType.IsArray()) { // need a static cast here to satisfy verifier // Example: Derived[] can be used in place of Base[] for all purposes except for LDELEMA <Base> // Even though it would be safe due to run time check, verifier requires that the static type of the array is Base[] // We do not know why we are casting, so to be safe, lets make the cast explicit. JIT elides such casts. EmitStaticCast(conversion.Type, conversion.Syntax); } return; } private void EmitExplicitReferenceConversion(BoundConversion conversion) { // turn operand into an O(operandType) // if the operand is already verifiably an O, we can use it as-is // otherwise we need to box it, so that verifier will start tracking an O if (!conversion.Operand.Type.IsVerifierReference()) { EmitBox(conversion.Operand.Type, conversion.Operand.Syntax); } // here we have O(operandType) that could be compatible with O(targetType) // // if target type is verifiably a reference type, we can just do a type check otherwise // we unbox which will both do the type check and start tracking actual target type in // verifier. if (conversion.Type.IsVerifierReference()) { _builder.EmitOpCode(ILOpCode.Castclass); EmitSymbolToken(conversion.Type, conversion.Syntax); } else { _builder.EmitOpCode(ILOpCode.Unbox_any); EmitSymbolToken(conversion.Type, conversion.Syntax); } } private void EmitEnumConversion(BoundConversion conversion) { // Nullable enumeration conversions should have already been lowered into // implicit or explicit nullable conversions. Debug.Assert(!conversion.Type.IsNullableType()); var fromType = conversion.Operand.Type; if (fromType.IsEnumType()) { fromType = ((NamedTypeSymbol)fromType).EnumUnderlyingType; } var fromPredefTypeKind = fromType.PrimitiveTypeCode; Debug.Assert(IsNumeric(fromType)); var toType = conversion.Type; if (toType.IsEnumType()) { toType = ((NamedTypeSymbol)toType).EnumUnderlyingType; } var toPredefTypeKind = toType.PrimitiveTypeCode; Debug.Assert(IsNumeric(toType)); _builder.EmitNumericConversion(fromPredefTypeKind, toPredefTypeKind, conversion.Checked); } private void EmitDelegateCreation(BoundExpression node, BoundExpression receiver, bool isExtensionMethod, MethodSymbol method, TypeSymbol delegateType, bool used) { var isStatic = receiver == null || (!isExtensionMethod && method.IsStatic); if (!used) { if (!isStatic) { EmitExpression(receiver, false); } return; } // emit the receiver if (isStatic) { _builder.EmitNullConstant(); if (method.IsAbstract || method.IsVirtual) { if (receiver is not BoundTypeExpression { Type: { TypeKind: TypeKind.TypeParameter } }) { throw ExceptionUtilities.Unreachable(); } _builder.EmitOpCode(ILOpCode.Constrained); EmitSymbolToken(receiver.Type, receiver.Syntax); } } else { EmitExpression(receiver, true); if (!receiver.Type.IsVerifierReference()) { EmitBox(receiver.Type, receiver.Syntax); } } // emit method pointer // Metadata Spec (II.14.6): // Delegates shall be declared sealed. // The Invoke method shall be virtual. if (!method.IsStatic && method.IsMetadataVirtual(this._module.SourceModule) && !method.ContainingType.IsDelegateType() && !receiver.SuppressVirtualCalls) { // NOTE: method.IsMetadataVirtual -> receiver != null _builder.EmitOpCode(ILOpCode.Dup); _builder.EmitOpCode(ILOpCode.Ldvirtftn); // substitute the method with original virtual method method = method.GetConstructedLeastOverriddenMethod(_method.ContainingType, requireSameReturnType: true); } else { _builder.EmitOpCode(ILOpCode.Ldftn); } EmitSymbolToken(method, node.Syntax, null); // call delegate constructor _builder.EmitOpCode(ILOpCode.Newobj, -1); // pop 2 args and push delegate object var ctor = DelegateConstructor(node.Syntax, delegateType); if ((object)ctor != null) EmitSymbolToken(ctor, node.Syntax, null); } private MethodSymbol DelegateConstructor(SyntaxNode syntax, TypeSymbol delegateType) { foreach (var possibleCtor in delegateType.GetMembers(WellKnownMemberNames.InstanceConstructorName)) { var m = possibleCtor as MethodSymbol; if ((object)m == null) continue; var parameters = m.Parameters; if (parameters.Length != 2) continue; if (parameters[0].Type.SpecialType != SpecialType.System_Object) continue; var p1t = parameters[1].Type.SpecialType; if (p1t == SpecialType.System_IntPtr || p1t == SpecialType.System_UIntPtr) { return m; } } // The delegate '{0}' does not have a valid constructor _diagnostics.Add(ErrorCode.ERR_BadDelegateConstructor, syntax.Location, delegateType); return null; } } }