From 818bca6808e43aa43cb99e77f3a75e47e0e396e2 Mon Sep 17 00:00:00 2001 From: Alexandre Mutel Date: Tue, 24 Oct 2023 23:46:43 +0200 Subject: [PATCH] Add support for UnsafeAccessorKind.FieldOffset (#45152) --- src/coreclr/vm/prestub.cpp | 55 ++++++++++++++----- .../UnsafeAccessorAttribute.cs | 7 ++- .../System.Runtime/ref/System.Runtime.cs | 3 +- .../UnsafeAccessors/UnsafeAccessorsTests.cs | 14 +++++ 4 files changed, 63 insertions(+), 16 deletions(-) diff --git a/src/coreclr/vm/prestub.cpp b/src/coreclr/vm/prestub.cpp index 90768a47b51578..ad2b1d1a24ec0e 100644 --- a/src/coreclr/vm/prestub.cpp +++ b/src/coreclr/vm/prestub.cpp @@ -1062,7 +1062,8 @@ namespace Method, // call instance method (`callvirt` in IL) StaticMethod, // call static method (`call` in IL) Field, // address of instance field (`ldflda` in IL) - StaticField // address of static field (`ldsflda` in IL) + StaticField, // address of static field (`ldsflda` in IL) + FieldOffset, // offset of field relative to the start of the object }; bool TryParseUnsafeAccessorAttribute( @@ -1316,7 +1317,8 @@ namespace STANDARD_VM_CONTRACT; _ASSERTE(fieldName != NULL); _ASSERTE(cxt.Kind == UnsafeAccessorKind::Field - || cxt.Kind == UnsafeAccessorKind::StaticField); + || cxt.Kind == UnsafeAccessorKind::StaticField + || cxt.Kind == UnsafeAccessorKind::FieldOffset); TypeHandle targetType = cxt.TargetType; _ASSERTE(!targetType.IsTypeDesc()); @@ -1325,15 +1327,30 @@ namespace targetType.AsMethodTable(), (cxt.IsTargetStatic ? ApproxFieldDescIterator::STATIC_FIELDS : ApproxFieldDescIterator::INSTANCE_FIELDS)); PTR_FieldDesc pField; - while ((pField = fdIterator.Next()) != NULL) + + if (cxt.Kind == UnsafeAccessorKind::FieldOffset) { - // Validate the name and target type match. - if (strcmp(fieldName, pField->GetName()) == 0 - && fieldType == pField->LookupFieldTypeHandle()) - { - cxt.TargetField = pField; - return true; - } + while ((pField = fdIterator.Next()) != NULL) + { + // Validate the name and target type match. + if (strcmp(fieldName, pField->GetName()) == 0) + { + cxt.TargetField = pField; + return true; + } + } + } + else + { + while ((pField = fdIterator.Next()) != NULL) + { + // Validate the name and target type match. + if (strcmp(fieldName, pField->GetName()) == 0 && fieldType == pField->LookupFieldTypeHandle()) + { + cxt.TargetField = pField; + return true; + } + } } return false; } @@ -1368,8 +1385,13 @@ namespace // during dispatch. UINT beginIndex = cxt.IsTargetStatic ? 1 : 0; UINT stubArgCount = cxt.DeclarationSig.NumFixedArgs(); - for (UINT i = beginIndex; i < stubArgCount; ++i) - pCode->EmitLDARG(i); + + // Don't load the first argument for a field offset, we are not using it. + if (cxt.Kind != UnsafeAccessorKind::FieldOffset) + { + for (UINT i = beginIndex; i < stubArgCount; ++i) + pCode->EmitLDARG(i); + } // Provide access to the target member UINT targetArgCount = stubArgCount - beginIndex; @@ -1396,6 +1418,10 @@ namespace _ASSERTE(cxt.TargetField != NULL); pCode->EmitLDSFLDA(pCode->GetToken(cxt.TargetField)); break; + case UnsafeAccessorKind::FieldOffset: + _ASSERTE(cxt.TargetField != NULL); + pCode->EmitLDC(cxt.TargetField->GetOffset()); + break; default: _ASSERTE(!"Unknown UnsafeAccessorKind"); } @@ -1519,16 +1545,17 @@ bool MethodDesc::TryGenerateUnsafeAccessor(DynamicResolver** resolver, COR_ILMET case UnsafeAccessorKind::Field: case UnsafeAccessorKind::StaticField: + case UnsafeAccessorKind::FieldOffset: // Field access requires a single argument for target type and a return type. if (argCount != 1 || firstArgType.IsNull() || context.DeclarationSig.IsReturnTypeVoid()) { ThrowHR(COR_E_BADIMAGEFORMAT, BFA_INVALID_UNSAFEACCESSOR); } - // The return type must be byref. + // The return type must be byref for field access. // If the non-static field access is for a // value type, the instance must be byref. - if (!retType.IsByRef() + if ((!retType.IsByRef() && kind != UnsafeAccessorKind::FieldOffset) || (kind == UnsafeAccessorKind::Field && firstArgType.IsValueType() && !firstArgType.IsByRef())) diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/UnsafeAccessorAttribute.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/UnsafeAccessorAttribute.cs index 476cb27fbf6eb1..599b805d9ef372 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/UnsafeAccessorAttribute.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/UnsafeAccessorAttribute.cs @@ -31,7 +31,12 @@ public enum UnsafeAccessorKind /// /// Provide access to a static field. /// - StaticField + StaticField, + + /// + /// Provide access to the offset of an instance field. + /// + FieldOffset }; /// diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index e3af0f096e4b62..90e76d87dead42 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -13304,7 +13304,8 @@ public enum UnsafeAccessorKind Method, StaticMethod, Field, - StaticField + StaticField, + FieldOffset }; [System.AttributeUsageAttribute(System.AttributeTargets.Struct)] public sealed partial class UnsafeValueTypeAttribute : System.Attribute diff --git a/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.cs b/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.cs index c524802b968be0..c719eea2a36a34 100644 --- a/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.cs +++ b/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.cs @@ -18,6 +18,7 @@ class UserDataClass { public const string StaticFieldName = nameof(_F); public const string FieldName = nameof(_f); + public const string FieldSecondName = nameof(_i); public const string StaticMethodName = nameof(_M); public const string MethodName = nameof(_m); public const string StaticMethodVoidName = nameof(_MVV); @@ -30,6 +31,7 @@ class UserDataClass private static string _F = PrivateStatic; private string _f; + private int _i; public string Value => _f; @@ -215,6 +217,18 @@ public static void Verify_AccessFieldClass() extern static ref string GetPrivateField(UserDataClass d); } + [Fact] + public static void Verify_AccessFieldOffsetClass() + { + Console.WriteLine($"Running {nameof(Verify_AccessFieldOffsetClass)}"); + + // Offset = method table pointer + first string pointer + Assert.Equal(IntPtr.Size + IntPtr.Size, GetPrivateFieldOffset()); + + [UnsafeAccessor(UnsafeAccessorKind.FieldOffset, Name=UserDataClass.FieldSecondName)] + extern static nint GetPrivateFieldOffset(UserDataClass? d = null); + } + [Fact] [ActiveIssue("https://github.com/dotnet/runtime/issues/92633")] public static void Verify_AccessStaticFieldGenericClass()