diff --git a/src/coreclr/inc/corinfo.h b/src/coreclr/inc/corinfo.h index 915ad1c4da44b5..0feb8619a8108e 100644 --- a/src/coreclr/inc/corinfo.h +++ b/src/coreclr/inc/corinfo.h @@ -3212,6 +3212,7 @@ class ICorDynamicInfo : public ICorStaticInfo CORINFO_FIELD_HANDLE field, uint8_t *buffer, int bufferSize, + int valueOffset = 0, bool ignoreMovableObjects = true ) = 0; diff --git a/src/coreclr/inc/icorjitinfoimpl_generated.h b/src/coreclr/inc/icorjitinfoimpl_generated.h index 62619e82d4de33..86ac07f8117dd2 100644 --- a/src/coreclr/inc/icorjitinfoimpl_generated.h +++ b/src/coreclr/inc/icorjitinfoimpl_generated.h @@ -625,6 +625,7 @@ bool getReadonlyStaticFieldValue( CORINFO_FIELD_HANDLE field, uint8_t* buffer, int bufferSize, + int valueOffset, bool ignoreMovableObjects) override; CORINFO_CLASS_HANDLE getStaticFieldCurrentClass( diff --git a/src/coreclr/inc/jiteeversionguid.h b/src/coreclr/inc/jiteeversionguid.h index 9211920488f6c7..b999fff982f968 100644 --- a/src/coreclr/inc/jiteeversionguid.h +++ b/src/coreclr/inc/jiteeversionguid.h @@ -43,11 +43,11 @@ typedef const GUID *LPCGUID; #define GUID_DEFINED #endif // !GUID_DEFINED -constexpr GUID JITEEVersionIdentifier = { /* da097b39-7f43-458a-990a-0b65406d5ff3 */ - 0xda097b39, - 0x7f43, - 0x458a, - {0x99, 0xa, 0xb, 0x65, 0x40, 0x6d, 0x5f, 0xf3} +constexpr GUID JITEEVersionIdentifier = { /* 0330a175-dd05-4760-840f-a1a4c47284d3 */ + 0x330a175, + 0xdd05, + 0x4760, + {0x84, 0xf, 0xa1, 0xa4, 0xc4, 0x72, 0x84, 0xd3} }; ////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/src/coreclr/jit/ICorJitInfo_wrapper_generated.hpp b/src/coreclr/jit/ICorJitInfo_wrapper_generated.hpp index 690210d7fb32f4..25b78bba0cae36 100644 --- a/src/coreclr/jit/ICorJitInfo_wrapper_generated.hpp +++ b/src/coreclr/jit/ICorJitInfo_wrapper_generated.hpp @@ -1488,10 +1488,11 @@ bool WrapICorJitInfo::getReadonlyStaticFieldValue( CORINFO_FIELD_HANDLE field, uint8_t* buffer, int bufferSize, + int valueOffset, bool ignoreMovableObjects) { API_ENTER(getReadonlyStaticFieldValue); - bool temp = wrapHnd->getReadonlyStaticFieldValue(field, buffer, bufferSize, ignoreMovableObjects); + bool temp = wrapHnd->getReadonlyStaticFieldValue(field, buffer, bufferSize, valueOffset, ignoreMovableObjects); API_LEAVE(getReadonlyStaticFieldValue); return temp; } diff --git a/src/coreclr/jit/assertionprop.cpp b/src/coreclr/jit/assertionprop.cpp index 30aaa8c5be1af2..59cf0dcb2992d9 100644 --- a/src/coreclr/jit/assertionprop.cpp +++ b/src/coreclr/jit/assertionprop.cpp @@ -3335,7 +3335,8 @@ GenTree* Compiler::optConstantAssertionProp(AssertionDsc* curAssertion, return nullptr; } - GenTree* newTree = tree; + GenTree* newTree = tree; + bool propagateType = false; // Update 'newTree' with the new value from our table // Typically newTree == tree and we are updating the node in place @@ -3364,9 +3365,16 @@ GenTree* Compiler::optConstantAssertionProp(AssertionDsc* curAssertion, case O2K_CONST_INT: // Don't propagate handles if we need to report relocs. - if (opts.compReloc && curAssertion->op2.HasIconFlag()) + if (opts.compReloc && curAssertion->op2.HasIconFlag() && curAssertion->op2.u1.iconVal != 0) { - return nullptr; + if (curAssertion->op2.GetIconFlag() == GTF_ICON_STATIC_HDL) + { + propagateType = true; + } + else + { + return nullptr; + } } // We assume that we do not try to do assertion prop on mismatched @@ -3394,6 +3402,11 @@ GenTree* Compiler::optConstantAssertionProp(AssertionDsc* curAssertion, // Conservatively don't allow propagation of ICON TYP_REF into BYREF return nullptr; } + propagateType = true; + } + + if (propagateType) + { newTree->ChangeType(tree->TypeGet()); } } diff --git a/src/coreclr/jit/codegencommon.cpp b/src/coreclr/jit/codegencommon.cpp index 1601d9fd566c43..32d5ec4c934bce 100644 --- a/src/coreclr/jit/codegencommon.cpp +++ b/src/coreclr/jit/codegencommon.cpp @@ -1107,7 +1107,11 @@ bool CodeGen::genCreateAddrMode( if (op2->IsIntCnsFitsInI32() && (op2->gtType != TYP_REF) && FitsIn(cns + op2->AsIntConCommon()->IconValue())) { // We should not be building address modes out of non-foldable constants - assert(op2->AsIntConCommon()->ImmedValCanBeFolded(compiler, addr->OperGet())); + if (!op2->AsIntConCommon()->ImmedValCanBeFolded(compiler, addr->OperGet())) + { + assert(compiler->opts.compReloc); + return false; + } /* We're adding a constant */ diff --git a/src/coreclr/jit/valuenum.cpp b/src/coreclr/jit/valuenum.cpp index 246694adc8d3a1..2ba4bd7f8c0afa 100644 --- a/src/coreclr/jit/valuenum.cpp +++ b/src/coreclr/jit/valuenum.cpp @@ -2113,7 +2113,7 @@ ValueNum ValueNumStore::VNForFunc(var_types typ, VNFunc func, ValueNum arg0VN) { uint8_t buffer[TARGET_POINTER_SIZE] = {0}; if (m_pComp->info.compCompHnd->getReadonlyStaticFieldValue(field, buffer, - TARGET_POINTER_SIZE, false)) + TARGET_POINTER_SIZE, 0, false)) { // In case of 64bit jit emitting 32bit codegen this handle will be 64bit // value holding 32bit handle with upper half zeroed (hence, "= NULL"). @@ -8494,6 +8494,55 @@ void Compiler::fgValueNumberSsaVarDef(GenTreeLclVarCommon* lcl) } } +//---------------------------------------------------------------------------------- +// fgGetStaticFieldSeqAndAddress: Try to obtain a constant address with a FieldSeq from the +// given tree. It can be either INT_CNS or e.g. ADD(INT_CNS, ADD(INT_CNS, INT_CNS)) +// tree where only one of the constants is expected to have a field sequence. +// +// Arguments: +// tree - tree node to inspect +// pAddress - [Out] resulting address with all offsets combined +// pFseq - [Out] field sequence +// +// Return Value: +// true if the given tree is a static field address +// +static bool fgGetStaticFieldSeqAndAddress(GenTree* tree, ssize_t* pAddress, FieldSeq** pFseq) +{ + ssize_t val = 0; + // Accumulate final offset + while (tree->OperIs(GT_ADD)) + { + GenTree* op1 = tree->gtGetOp1(); + GenTree* op2 = tree->gtGetOp2(); + if (op1->IsCnsIntOrI() && (op1->AsIntCon()->gtFieldSeq == nullptr)) + { + val += op1->AsIntCon()->IconValue(); + tree = op2; + } + else if (op2->IsCnsIntOrI() && (op2->AsIntCon()->gtFieldSeq == nullptr)) + { + val += op2->AsIntCon()->IconValue(); + tree = op1; + } + else + { + // We only inspect constants and additions + return false; + } + } + + // Base address is expected to be static field's address + if ((tree->IsCnsIntOrI()) && (tree->AsIntCon()->gtFieldSeq != nullptr) && + (tree->AsIntCon()->gtFieldSeq->GetKind() == FieldSeq::FieldKind::SimpleStaticKnownAddress)) + { + *pFseq = tree->AsIntCon()->gtFieldSeq; + *pAddress = tree->AsIntCon()->IconValue() + val; + return true; + } + return false; +} + //---------------------------------------------------------------------------------- // fgValueNumberConstLoad: Try to detect const_immutable_array[cns_index] tree // and apply a constant VN representing given element at cns_index in that array. @@ -8506,9 +8555,104 @@ void Compiler::fgValueNumberSsaVarDef(GenTreeLclVarCommon* lcl) // bool Compiler::fgValueNumberConstLoad(GenTreeIndir* tree) { + if (!tree->gtVNPair.BothEqual()) + { + return false; + } + + // First, let's check if we can detect RVA[const_index] pattern to fold, e.g.: + // + // static ReadOnlySpan RVA => new sbyte[] { -100, 100 } + // + // sbyte GetVal() => RVA[1]; // fold to '100' + // + ssize_t address = 0; + FieldSeq* fieldSeq = nullptr; + if (fgGetStaticFieldSeqAndAddress(tree->gtGetOp1(), &address, &fieldSeq)) + { + assert(fieldSeq->GetKind() == FieldSeq::FieldKind::SimpleStaticKnownAddress); + CORINFO_FIELD_HANDLE fieldHandle = fieldSeq->GetFieldHandle(); + + ssize_t byteOffset = address - fieldSeq->GetOffset(); + int size = (int)genTypeSize(tree->TypeGet()); + const int maxElementSize = sizeof(int64_t); + if ((fieldHandle != nullptr) && (size > 0) && (size <= maxElementSize) && ((size_t)byteOffset < INT_MAX)) + { + uint8_t buffer[maxElementSize] = {0}; + if (info.compCompHnd->getReadonlyStaticFieldValue(fieldHandle, (uint8_t*)&buffer, size, (int)byteOffset)) + { + // For now we only support these primitives, we can extend this list to FP, SIMD and structs in future. + switch (tree->TypeGet()) + { +#define READ_VALUE(typ) \ + typ val = 0; \ + memcpy(&val, buffer, sizeof(typ)); + + case TYP_BOOL: + case TYP_UBYTE: + { + READ_VALUE(uint8_t); + tree->gtVNPair.SetBoth(vnStore->VNForIntCon(val)); + return true; + } + case TYP_BYTE: + { + READ_VALUE(int8_t); + tree->gtVNPair.SetBoth(vnStore->VNForIntCon(val)); + return true; + } + case TYP_SHORT: + { + READ_VALUE(int16_t); + tree->gtVNPair.SetBoth(vnStore->VNForIntCon(val)); + return true; + } + case TYP_USHORT: + { + READ_VALUE(uint16_t); + tree->gtVNPair.SetBoth(vnStore->VNForIntCon(val)); + return true; + } + case TYP_INT: + { + READ_VALUE(int32_t); + tree->gtVNPair.SetBoth(vnStore->VNForIntCon(val)); + return true; + } + case TYP_UINT: + { + READ_VALUE(uint32_t); + tree->gtVNPair.SetBoth(vnStore->VNForIntCon(val)); + return true; + } + case TYP_LONG: + { + READ_VALUE(int64_t); + tree->gtVNPair.SetBoth(vnStore->VNForLongCon(val)); + return true; + } + case TYP_ULONG: + { + READ_VALUE(uint64_t); + tree->gtVNPair.SetBoth(vnStore->VNForLongCon(val)); + return true; + } + default: + break; + } + } + } + } + + // Throughput check, the logic below is only for USHORT (char) + if (!tree->TypeIs(TYP_USHORT)) + { + return false; + } + ValueNum addrVN = tree->gtGetOp1()->gtVNPair.GetLiberal(); VNFuncApp funcApp; - if (!tree->TypeIs(TYP_USHORT) || !tree->gtVNPair.BothEqual() || !vnStore->GetVNFunc(addrVN, &funcApp)) + if (!vnStore->GetVNFunc(addrVN, &funcApp)) { return false; } diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs index 87eacb8f9c418a..fa33a55b7098dc 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs @@ -4079,5 +4079,25 @@ private bool notifyInstructionSetUsage(InstructionSet instructionSet, bool suppo return supportEnabled ? _compilation.InstructionSetSupport.IsInstructionSetSupported(instructionSet) : false; } #endif + + private static bool TryReadRvaFieldData(FieldDesc field, byte* buffer, int bufferSize, int valueOffset) + { + Debug.Assert(buffer != null); + Debug.Assert(bufferSize > 0); + Debug.Assert(valueOffset >= 0); + Debug.Assert(field.IsStatic); + Debug.Assert(field.HasRva); + + if (!field.IsThreadStatic && field.IsInitOnly && field is EcmaField ecmaField) + { + ReadOnlySpan rvaData = ecmaField.GetFieldRvaData(); + if (rvaData.Length >= bufferSize && valueOffset <= rvaData.Length - bufferSize) + { + rvaData.Slice(valueOffset, bufferSize).CopyTo(new Span(buffer, bufferSize)); + return true; + } + } + return false; + } } } diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoImpl_generated.cs b/src/coreclr/tools/Common/JitInterface/CorInfoImpl_generated.cs index cc8d6c17e1f29b..bfca28ea857370 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoImpl_generated.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoImpl_generated.cs @@ -2244,12 +2244,12 @@ private static uint _getClassDomainID(IntPtr thisHandle, IntPtr* ppException, CO } [UnmanagedCallersOnly] - private static byte _getReadonlyStaticFieldValue(IntPtr thisHandle, IntPtr* ppException, CORINFO_FIELD_STRUCT_* field, byte* buffer, int bufferSize, byte ignoreMovableObjects) + private static byte _getReadonlyStaticFieldValue(IntPtr thisHandle, IntPtr* ppException, CORINFO_FIELD_STRUCT_* field, byte* buffer, int bufferSize, int valueOffset, byte ignoreMovableObjects) { var _this = GetThis(thisHandle); try { - return _this.getReadonlyStaticFieldValue(field, buffer, bufferSize, ignoreMovableObjects != 0) ? (byte)1 : (byte)0; + return _this.getReadonlyStaticFieldValue(field, buffer, bufferSize, valueOffset, ignoreMovableObjects != 0) ? (byte)1 : (byte)0; } catch (Exception ex) { @@ -2838,7 +2838,7 @@ private static IntPtr GetUnmanagedCallbacks() callbacks[148] = (delegate* unmanaged)&_isRIDClassDomainID; callbacks[149] = (delegate* unmanaged)&_getClassDomainID; callbacks[150] = (delegate* unmanaged)&_getFieldAddress; - callbacks[151] = (delegate* unmanaged)&_getReadonlyStaticFieldValue; + callbacks[151] = (delegate* unmanaged)&_getReadonlyStaticFieldValue; callbacks[152] = (delegate* unmanaged)&_getStaticFieldCurrentClass; callbacks[153] = (delegate* unmanaged)&_getVarArgsHandle; callbacks[154] = (delegate* unmanaged)&_canGetVarArgsHandle; diff --git a/src/coreclr/tools/Common/JitInterface/ThunkGenerator/ThunkInput.txt b/src/coreclr/tools/Common/JitInterface/ThunkGenerator/ThunkInput.txt index 11f2c2b997a706..9be26985000fcf 100644 --- a/src/coreclr/tools/Common/JitInterface/ThunkGenerator/ThunkInput.txt +++ b/src/coreclr/tools/Common/JitInterface/ThunkGenerator/ThunkInput.txt @@ -308,7 +308,7 @@ FUNCTIONS bool isRIDClassDomainID(CORINFO_CLASS_HANDLE cls); unsigned getClassDomainID (CORINFO_CLASS_HANDLE cls, void **ppIndirection); void* getFieldAddress(CORINFO_FIELD_HANDLE field, VOIDSTARSTAR ppIndirection); - bool getReadonlyStaticFieldValue(CORINFO_FIELD_HANDLE field, uint8_t *buffer, int bufferSize, bool ignoreMovableObjects); + bool getReadonlyStaticFieldValue(CORINFO_FIELD_HANDLE field, uint8_t *buffer, int bufferSize, int valueOffset, bool ignoreMovableObjects); CORINFO_CLASS_HANDLE getStaticFieldCurrentClass(CORINFO_FIELD_HANDLE field, BoolStar pIsSpeculative); CORINFO_VARARGS_HANDLE getVarArgsHandle(CORINFO_SIG_INFO *pSig, void **ppIndirection); bool canGetVarArgsHandle(CORINFO_SIG_INFO *pSig); diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs index 826beb13d111ad..263815aa54574b 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs @@ -2998,8 +2998,16 @@ private int getExactClasses(CORINFO_CLASS_STRUCT_* baseType, int maxExactClasses return 0; } - private bool getReadonlyStaticFieldValue(CORINFO_FIELD_STRUCT_* fieldHandle, byte* buffer, int bufferSize, bool ignoreMovableObjects) + private bool getReadonlyStaticFieldValue(CORINFO_FIELD_STRUCT_* fieldHandle, byte* buffer, int bufferSize, int valueOffset, bool ignoreMovableObjects) { + Debug.Assert(fieldHandle != null); + FieldDesc field = HandleToObject(fieldHandle); + + // For crossgen2 we only support RVA fields + if (_compilation.NodeFactory.CompilationModuleGroup.VersionsWithType(field.OwningType) && field.HasRva) + { + return TryReadRvaFieldData(field, buffer, bufferSize, valueOffset); + } return false; } diff --git a/src/coreclr/tools/aot/ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.cs b/src/coreclr/tools/aot/ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.cs index 1738831d07fda4..55c08d549de0b1 100644 --- a/src/coreclr/tools/aot/ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.cs +++ b/src/coreclr/tools/aot/ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.cs @@ -13,6 +13,7 @@ using ILCompiler; using ILCompiler.DependencyAnalysis; +using Internal.TypeSystem.Ecma; #if SUPPORT_JIT using MethodCodeNode = Internal.Runtime.JitSupport.JitMethodCodeNode; @@ -2213,17 +2214,24 @@ private int getExactClasses(CORINFO_CLASS_STRUCT_* baseType, int maxExactClasses return index; } - private bool getReadonlyStaticFieldValue(CORINFO_FIELD_STRUCT_* fieldHandle, byte* buffer, int bufferSize, bool ignoreMovableObjects) + private bool getReadonlyStaticFieldValue(CORINFO_FIELD_STRUCT_* fieldHandle, byte* buffer, int bufferSize, int valueOffset, bool ignoreMovableObjects) { Debug.Assert(fieldHandle != null); Debug.Assert(buffer != null); Debug.Assert(bufferSize > 0); + Debug.Assert(valueOffset >= 0); FieldDesc field = HandleToObject(fieldHandle); Debug.Assert(field.IsStatic); + if (!field.IsThreadStatic && field.IsInitOnly && field.OwningType is MetadataType owningType) { + if (field.HasRva) + { + return TryReadRvaFieldData(field, buffer, bufferSize, valueOffset); + } + PreinitializationManager preinitManager = _compilation.NodeFactory.PreinitializationManager; if (preinitManager.IsPreinitialized(owningType)) { @@ -2234,6 +2242,7 @@ private bool getReadonlyStaticFieldValue(CORINFO_FIELD_STRUCT_* fieldHandle, byt if (value == null) { + Debug.Assert(valueOffset == 0); Debug.Assert(bufferSize == targetPtrSize); // Write "null" to buffer @@ -2246,13 +2255,15 @@ private bool getReadonlyStaticFieldValue(CORINFO_FIELD_STRUCT_* fieldHandle, byt switch (data) { case byte[] bytes: - Debug.Assert(bufferSize == bytes.Length); - - // Ensure we have enough room in the buffer, it can be a large struct - bytes.AsSpan().CopyTo(new Span(buffer, bufferSize)); - return true; + if (bytes.Length >= bufferSize && valueOffset <= bytes.Length - bufferSize) + { + bytes.AsSpan(valueOffset, bufferSize).CopyTo(new Span(buffer, bufferSize)); + return true; + } + return false; case FrozenObjectNode or FrozenStringNode: + Debug.Assert(valueOffset == 0); Debug.Assert(bufferSize == targetPtrSize); // save handle's value to buffer diff --git a/src/coreclr/tools/aot/jitinterface/jitinterface_generated.h b/src/coreclr/tools/aot/jitinterface/jitinterface_generated.h index 1fcfd049f6bbb3..c37d7aff4e9075 100644 --- a/src/coreclr/tools/aot/jitinterface/jitinterface_generated.h +++ b/src/coreclr/tools/aot/jitinterface/jitinterface_generated.h @@ -162,7 +162,7 @@ struct JitInterfaceCallbacks bool (* isRIDClassDomainID)(void * thisHandle, CorInfoExceptionClass** ppException, CORINFO_CLASS_HANDLE cls); unsigned (* getClassDomainID)(void * thisHandle, CorInfoExceptionClass** ppException, CORINFO_CLASS_HANDLE cls, void** ppIndirection); void* (* getFieldAddress)(void * thisHandle, CorInfoExceptionClass** ppException, CORINFO_FIELD_HANDLE field, void** ppIndirection); - bool (* getReadonlyStaticFieldValue)(void * thisHandle, CorInfoExceptionClass** ppException, CORINFO_FIELD_HANDLE field, uint8_t* buffer, int bufferSize, bool ignoreMovableObjects); + bool (* getReadonlyStaticFieldValue)(void * thisHandle, CorInfoExceptionClass** ppException, CORINFO_FIELD_HANDLE field, uint8_t* buffer, int bufferSize, int valueOffset, bool ignoreMovableObjects); CORINFO_CLASS_HANDLE (* getStaticFieldCurrentClass)(void * thisHandle, CorInfoExceptionClass** ppException, CORINFO_FIELD_HANDLE field, bool* pIsSpeculative); CORINFO_VARARGS_HANDLE (* getVarArgsHandle)(void * thisHandle, CorInfoExceptionClass** ppException, CORINFO_SIG_INFO* pSig, void** ppIndirection); bool (* canGetVarArgsHandle)(void * thisHandle, CorInfoExceptionClass** ppException, CORINFO_SIG_INFO* pSig); @@ -1665,10 +1665,11 @@ class JitInterfaceWrapper : public ICorJitInfo CORINFO_FIELD_HANDLE field, uint8_t* buffer, int bufferSize, + int valueOffset, bool ignoreMovableObjects) { CorInfoExceptionClass* pException = nullptr; - bool temp = _callbacks->getReadonlyStaticFieldValue(_thisHandle, &pException, field, buffer, bufferSize, ignoreMovableObjects); + bool temp = _callbacks->getReadonlyStaticFieldValue(_thisHandle, &pException, field, buffer, bufferSize, valueOffset, ignoreMovableObjects); if (pException != nullptr) throw pException; return temp; } diff --git a/src/coreclr/tools/superpmi/superpmi-shared/lwmlist.h b/src/coreclr/tools/superpmi/superpmi-shared/lwmlist.h index a936ac6dad8d3f..4bb576b284b779 100644 --- a/src/coreclr/tools/superpmi/superpmi-shared/lwmlist.h +++ b/src/coreclr/tools/superpmi/superpmi-shared/lwmlist.h @@ -78,7 +78,7 @@ LWM(GetDelegateCtor, Agnostic_GetDelegateCtorIn, Agnostic_GetDelegateCtorOut) LWM(GetEEInfo, DWORD, Agnostic_CORINFO_EE_INFO) LWM(GetEHinfo, DLD, Agnostic_CORINFO_EH_CLAUSE) LWM(GetFieldAddress, DWORDLONG, Agnostic_GetFieldAddress) -LWM(GetReadonlyStaticFieldValue, DLDD, DD) +LWM(GetReadonlyStaticFieldValue, DLDDD, DD) LWM(GetStaticFieldCurrentClass, DWORDLONG, Agnostic_GetStaticFieldCurrentClass) LWM(GetFieldClass, DWORDLONG, DWORDLONG) LWM(GetFieldInClass, DLD, DWORDLONG) diff --git a/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.cpp b/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.cpp index eef0d7f715a7b2..fd078673e717d7 100644 --- a/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.cpp +++ b/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.cpp @@ -3569,16 +3569,17 @@ void* MethodContext::repGetFieldAddress(CORINFO_FIELD_HANDLE field, void** ppInd return (void*)value.fieldAddress; } -void MethodContext::recGetReadonlyStaticFieldValue(CORINFO_FIELD_HANDLE field, uint8_t* buffer, int bufferSize, bool ignoreMovableObjects, bool result) +void MethodContext::recGetReadonlyStaticFieldValue(CORINFO_FIELD_HANDLE field, uint8_t* buffer, int bufferSize, int valueOffset, bool ignoreMovableObjects, bool result) { if (GetReadonlyStaticFieldValue == nullptr) - GetReadonlyStaticFieldValue = new LightWeightMap(); + GetReadonlyStaticFieldValue = new LightWeightMap(); - DLDD key; + DLDDD key; ZeroMemory(&key, sizeof(key)); key.A = CastHandle(field); key.B = (DWORD)bufferSize; key.C = (DWORD)ignoreMovableObjects; + key.D = (DWORD)valueOffset; DWORD tmpBuf = (DWORD)-1; if (buffer != nullptr && result) @@ -3591,18 +3592,19 @@ void MethodContext::recGetReadonlyStaticFieldValue(CORINFO_FIELD_HANDLE field, u GetReadonlyStaticFieldValue->Add(key, value); DEBUG_REC(dmpGetReadonlyStaticFieldValue(key, value)); } -void MethodContext::dmpGetReadonlyStaticFieldValue(DLDD key, DD value) +void MethodContext::dmpGetReadonlyStaticFieldValue(DLDDD key, DD value) { - printf("GetReadonlyStaticFieldValue key fld-%016llX bufSize-%u, ignoremovable-%u, result-%u", key.A, key.B, key.C, value.A); + printf("GetReadonlyStaticFieldValue key fld-%016llX bufSize-%u, ignoremovable-%u, valOffset-%u result-%u", key.A, key.B, key.C, key.D, value.A); GetReadonlyStaticFieldValue->Unlock(); } -bool MethodContext::repGetReadonlyStaticFieldValue(CORINFO_FIELD_HANDLE field, uint8_t* buffer, int bufferSize, bool ignoreMovableObjects) +bool MethodContext::repGetReadonlyStaticFieldValue(CORINFO_FIELD_HANDLE field, uint8_t* buffer, int bufferSize, int valueOffset, bool ignoreMovableObjects) { - DLDD key; + DLDDD key; ZeroMemory(&key, sizeof(key)); key.A = CastHandle(field); key.B = (DWORD)bufferSize; key.C = (DWORD)ignoreMovableObjects; + key.D = (DWORD)valueOffset; DD value = LookupByKeyOrMiss(GetReadonlyStaticFieldValue, key, ": key %016llX", key.A); diff --git a/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.h b/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.h index b1ef506a95c3ce..d943d3f7f3950e 100644 --- a/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.h +++ b/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.h @@ -492,9 +492,9 @@ class MethodContext void dmpGetFieldAddress(DWORDLONG key, const Agnostic_GetFieldAddress& value); void* repGetFieldAddress(CORINFO_FIELD_HANDLE field, void** ppIndirection); - void recGetReadonlyStaticFieldValue(CORINFO_FIELD_HANDLE field, uint8_t* buffer, int bufferSize, bool ignoreMovableObjects, bool result); - void dmpGetReadonlyStaticFieldValue(DLDD key, DD value); - bool repGetReadonlyStaticFieldValue(CORINFO_FIELD_HANDLE field, uint8_t* buffer, int bufferSize, bool ignoreMovableObjects); + void recGetReadonlyStaticFieldValue(CORINFO_FIELD_HANDLE field, uint8_t* buffer, int bufferSize, int valueOffset, bool ignoreMovableObjects, bool result); + void dmpGetReadonlyStaticFieldValue(DLDDD key, DD value); + bool repGetReadonlyStaticFieldValue(CORINFO_FIELD_HANDLE field, uint8_t* buffer, int bufferSize, int valueOffset, bool ignoreMovableObjects); void recGetStaticFieldCurrentClass(CORINFO_FIELD_HANDLE field, bool isSpeculative, CORINFO_CLASS_HANDLE result); void dmpGetStaticFieldCurrentClass(DWORDLONG key, const Agnostic_GetStaticFieldCurrentClass& value); diff --git a/src/coreclr/tools/superpmi/superpmi-shim-collector/icorjitinfo.cpp b/src/coreclr/tools/superpmi/superpmi-shim-collector/icorjitinfo.cpp index dc7e4bf02952a4..90b5b4d448e925 100644 --- a/src/coreclr/tools/superpmi/superpmi-shim-collector/icorjitinfo.cpp +++ b/src/coreclr/tools/superpmi/superpmi-shim-collector/icorjitinfo.cpp @@ -1713,11 +1713,11 @@ void* interceptor_ICJI::getFieldAddress(CORINFO_FIELD_HANDLE field, void** ppInd return temp; } -bool interceptor_ICJI::getReadonlyStaticFieldValue(CORINFO_FIELD_HANDLE field, uint8_t* buffer, int bufferSize, bool ignoreMovableObjects) +bool interceptor_ICJI::getReadonlyStaticFieldValue(CORINFO_FIELD_HANDLE field, uint8_t* buffer, int bufferSize, int valueOffset, bool ignoreMovableObjects) { mc->cr->AddCall("getReadonlyStaticFieldValue"); - bool result = original_ICorJitInfo->getReadonlyStaticFieldValue(field, buffer, bufferSize, ignoreMovableObjects); - mc->recGetReadonlyStaticFieldValue(field, buffer, bufferSize, ignoreMovableObjects, result); + bool result = original_ICorJitInfo->getReadonlyStaticFieldValue(field, buffer, bufferSize, valueOffset, ignoreMovableObjects); + mc->recGetReadonlyStaticFieldValue(field, buffer, bufferSize, valueOffset, ignoreMovableObjects, result); return result; } diff --git a/src/coreclr/tools/superpmi/superpmi-shim-counter/icorjitinfo_generated.cpp b/src/coreclr/tools/superpmi/superpmi-shim-counter/icorjitinfo_generated.cpp index 50212182b68647..9bea437e950ef8 100644 --- a/src/coreclr/tools/superpmi/superpmi-shim-counter/icorjitinfo_generated.cpp +++ b/src/coreclr/tools/superpmi/superpmi-shim-counter/icorjitinfo_generated.cpp @@ -1220,10 +1220,11 @@ bool interceptor_ICJI::getReadonlyStaticFieldValue( CORINFO_FIELD_HANDLE field, uint8_t* buffer, int bufferSize, + int valueOffset, bool ignoreMovableObjects) { mcs->AddCall("getReadonlyStaticFieldValue"); - return original_ICorJitInfo->getReadonlyStaticFieldValue(field, buffer, bufferSize, ignoreMovableObjects); + return original_ICorJitInfo->getReadonlyStaticFieldValue(field, buffer, bufferSize, valueOffset, ignoreMovableObjects); } CORINFO_CLASS_HANDLE interceptor_ICJI::getStaticFieldCurrentClass( diff --git a/src/coreclr/tools/superpmi/superpmi-shim-simple/icorjitinfo_generated.cpp b/src/coreclr/tools/superpmi/superpmi-shim-simple/icorjitinfo_generated.cpp index fd3edaa53f2f24..b9c55b95d98d0a 100644 --- a/src/coreclr/tools/superpmi/superpmi-shim-simple/icorjitinfo_generated.cpp +++ b/src/coreclr/tools/superpmi/superpmi-shim-simple/icorjitinfo_generated.cpp @@ -1069,9 +1069,10 @@ bool interceptor_ICJI::getReadonlyStaticFieldValue( CORINFO_FIELD_HANDLE field, uint8_t* buffer, int bufferSize, + int valueOffset, bool ignoreMovableObjects) { - return original_ICorJitInfo->getReadonlyStaticFieldValue(field, buffer, bufferSize, ignoreMovableObjects); + return original_ICorJitInfo->getReadonlyStaticFieldValue(field, buffer, bufferSize, valueOffset, ignoreMovableObjects); } CORINFO_CLASS_HANDLE interceptor_ICJI::getStaticFieldCurrentClass( diff --git a/src/coreclr/tools/superpmi/superpmi/icorjitinfo.cpp b/src/coreclr/tools/superpmi/superpmi/icorjitinfo.cpp index f5970f4f80a453..9dc9f922b527ee 100644 --- a/src/coreclr/tools/superpmi/superpmi/icorjitinfo.cpp +++ b/src/coreclr/tools/superpmi/superpmi/icorjitinfo.cpp @@ -1488,10 +1488,10 @@ void* MyICJI::getFieldAddress(CORINFO_FIELD_HANDLE field, void** ppIndirection) return jitInstance->mc->repGetFieldAddress(field, ppIndirection); } -bool MyICJI::getReadonlyStaticFieldValue(CORINFO_FIELD_HANDLE field, uint8_t* buffer, int bufferSize, bool ignoreMovableObjects) +bool MyICJI::getReadonlyStaticFieldValue(CORINFO_FIELD_HANDLE field, uint8_t* buffer, int bufferSize, int valueOffset, bool ignoreMovableObjects) { jitInstance->mc->cr->AddCall("getReadonlyStaticFieldValue"); - return jitInstance->mc->repGetReadonlyStaticFieldValue(field, buffer, bufferSize, ignoreMovableObjects); + return jitInstance->mc->repGetReadonlyStaticFieldValue(field, buffer, bufferSize, valueOffset, ignoreMovableObjects); } // return the class handle for the current value of a static field diff --git a/src/coreclr/vm/jitinterface.cpp b/src/coreclr/vm/jitinterface.cpp index c1553c14f6c031..a1dd582f554203 100644 --- a/src/coreclr/vm/jitinterface.cpp +++ b/src/coreclr/vm/jitinterface.cpp @@ -11957,7 +11957,7 @@ void* CEEJitInfo::getFieldAddress(CORINFO_FIELD_HANDLE fieldHnd, return result; } -bool CEEInfo::getReadonlyStaticFieldValue(CORINFO_FIELD_HANDLE fieldHnd, uint8_t* buffer, int bufferSize, bool ignoreMovableObjects) +bool CEEInfo::getReadonlyStaticFieldValue(CORINFO_FIELD_HANDLE fieldHnd, uint8_t* buffer, int bufferSize, int valueOffset, bool ignoreMovableObjects) { CONTRACTL { THROWS; @@ -11968,6 +11968,7 @@ bool CEEInfo::getReadonlyStaticFieldValue(CORINFO_FIELD_HANDLE fieldHnd, uint8_t _ASSERT(fieldHnd != NULL); _ASSERT(buffer != NULL); _ASSERT(bufferSize > 0); + _ASSERT(valueOffset >= 0); bool result = false; @@ -11975,7 +11976,6 @@ bool CEEInfo::getReadonlyStaticFieldValue(CORINFO_FIELD_HANDLE fieldHnd, uint8_t FieldDesc* field = (FieldDesc*)fieldHnd; _ASSERTE(field->IsStatic()); - _ASSERTE((unsigned)bufferSize == field->GetSize()); MethodTable* pEnclosingMT = field->GetEnclosingMethodTable(); _ASSERTE(!pEnclosingMT->IsSharedByGenericInstantiations()); @@ -11991,6 +11991,10 @@ bool CEEInfo::getReadonlyStaticFieldValue(CORINFO_FIELD_HANDLE fieldHnd, uint8_t GCX_COOP(); if (field->IsObjRef()) { + _ASSERT(!field->IsRVA()); + _ASSERT(valueOffset == 0); // there is no point in returning a chunk of a gc handle + _ASSERT((UINT)bufferSize == field->GetSize()); + OBJECTREF fieldObj = field->GetStaticOBJECTREF(); if (fieldObj != NULL) { @@ -12021,10 +12025,17 @@ bool CEEInfo::getReadonlyStaticFieldValue(CORINFO_FIELD_HANDLE fieldHnd, uint8_t } else { - void* fldAddr = field->GetCurrentStaticAddress(); - _ASSERTE(fldAddr != nullptr); - memcpy(buffer, fldAddr, bufferSize); - result = true; + // Either RVA, primitive or struct (or even part of that struct) + size_t baseAddr = (size_t)field->GetCurrentStaticAddress(); + UINT size = field->GetSize(); + _ASSERTE(baseAddr > 0); + _ASSERTE(size > 0); + + if (size >= (UINT)bufferSize && valueOffset >= 0 && (UINT)valueOffset <= size - (UINT)bufferSize) + { + memcpy(buffer, (uint8_t*)baseAddr + valueOffset, bufferSize); + result = true; + } } } diff --git a/src/tests/JIT/opt/ValueNumbering/ConstIndexRVA.cs b/src/tests/JIT/opt/ValueNumbering/ConstIndexRVA.cs new file mode 100644 index 00000000000000..e16edb9f1a67ba --- /dev/null +++ b/src/tests/JIT/opt/ValueNumbering/ConstIndexRVA.cs @@ -0,0 +1,113 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +class RvaTests +{ + static int Main() + { + if (!BitConverter.IsLittleEndian) + { + // Test is not BE friendly + return 100; + } + + int testMethods = 0; + foreach (MethodInfo mth in typeof(RvaTests) + .GetMethods(BindingFlags.Static | BindingFlags.NonPublic) + .Where(m => m.Name.StartsWith("Test_"))) + { + mth.Invoke(null, null); + testMethods++; + } + return testMethods == 25 ? 100 : -100; + } + + static ReadOnlySpan RVA1 => new byte[] + { + 0x9c, 0x00, 0x01, 0x10, + 0x80, 0xAA, 0xAB, 0xFF, + 0x9b, 0x02, 0x03, 0x14, + 0x85, 0xA6, 0xA7, 0xF9, + }; + static ReadOnlySpan RVA2 => new sbyte[] { -100, 100, 0, -128, 127 }; + static ReadOnlySpan RVA3 => new[] { true, false }; + + [MethodImpl(MethodImplOptions.NoInlining)] static void Test_1() => AssertEquals((int)RVA1[0], (int)0x9c); + [MethodImpl(MethodImplOptions.NoInlining)] static void Test_2() => AssertEquals(RVA1[1], 0x00); + [MethodImpl(MethodImplOptions.NoInlining)] static void Test_3() => AssertEquals(RVA1[4], 0x80); + [MethodImpl(MethodImplOptions.NoInlining)] static void Test_4() => AssertEquals(RVA1[RVA1.Length - 1], 0xF9); + [MethodImpl(MethodImplOptions.NoInlining)] static void Test_5() => ThrowsOOB(() => Consume(RVA1[-1])); + [MethodImpl(MethodImplOptions.NoInlining)] static void Test_6() => ThrowsOOB(() => Consume(RVA1[0xFF])); + [MethodImpl(MethodImplOptions.NoInlining)] static void Test_7() => ThrowsOOB(() => Consume(RVA1[RVA1.Length])); + [MethodImpl(MethodImplOptions.NoInlining)] static void Test_8() => AssertEquals((long)RVA2[0], -100); + [MethodImpl(MethodImplOptions.NoInlining)] static void Test_9() => AssertEquals(RVA2[1], 100); + [MethodImpl(MethodImplOptions.NoInlining)] static void Test_10() => AssertEquals(RVA2[^1], 127); + [MethodImpl(MethodImplOptions.NoInlining)] static void Test_11() => ThrowsOOB(() => Consume(RVA2[-int.MaxValue])); + [MethodImpl(MethodImplOptions.NoInlining)] static void Test_12() => ThrowsOOB(() => Consume(RVA2[0xFF])); + [MethodImpl(MethodImplOptions.NoInlining)] static void Test_13() => ThrowsOOB(() => Consume(RVA2[RVA2.Length])); + [MethodImpl(MethodImplOptions.NoInlining)] static void Test_14() => AssertEquals(RVA3[0], true); + [MethodImpl(MethodImplOptions.NoInlining)] static void Test_15() => AssertEquals(RVA3[1], false); + [MethodImpl(MethodImplOptions.NoInlining)] static void Test_16() => AssertEquals(Unsafe.ReadUnaligned(ref Unsafe.Add(ref MemoryMarshal.GetReference(RVA1), 0)), 268501148); + [MethodImpl(MethodImplOptions.NoInlining)] static void Test_17() => AssertEquals(Unsafe.ReadUnaligned(ref Unsafe.Add(ref MemoryMarshal.GetReference(RVA1), 1)), 2148532480); + [MethodImpl(MethodImplOptions.NoInlining)] static void Test_18() => AssertEquals(Unsafe.ReadUnaligned(ref Unsafe.Add(ref MemoryMarshal.GetReference(RVA1), 10)), -1501228029); + [MethodImpl(MethodImplOptions.NoInlining)] static void Test_19() => AssertEquals(Unsafe.ReadUnaligned(ref Unsafe.Add(ref MemoryMarshal.GetReference(RVA1), 0)), 156); + [MethodImpl(MethodImplOptions.NoInlining)] static void Test_20() => AssertEquals(Unsafe.ReadUnaligned(ref Unsafe.Add(ref MemoryMarshal.GetReference(RVA1), 1)), 11240891943721369856); + [MethodImpl(MethodImplOptions.NoInlining)] static void Test_21() => AssertEquals(Unsafe.ReadUnaligned(ref Unsafe.Add(ref MemoryMarshal.GetReference(RVA1), 4)), 1441999174721317504); + [MethodImpl(MethodImplOptions.NoInlining)] static void Test_22() => AssertEquals(Unsafe.ReadUnaligned(ref Unsafe.Add(ref MemoryMarshal.GetReference(RVA1), 0)), new MyStruct(-23737906019368804)); + [MethodImpl(MethodImplOptions.NoInlining)] static void Test_23() => AssertEquals(Unsafe.ReadUnaligned(ref Unsafe.Add(ref MemoryMarshal.GetReference(RVA1), 0)), new Guid("1001009c-aa80-ffab-9b02-031485a6a7f9")); + + [MethodImpl(MethodImplOptions.NoInlining)] + static int Test_24() // AssertProp test + { + byte x = RVA1[1]; + if (x > 100) + { + Consume(x); + } + return x; + } + [MethodImpl(MethodImplOptions.NoInlining)] + static int Test_25() // AssertProp test + { + sbyte x = RVA2[0]; + if (x > 100) + { + Consume(x); + } + return x; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static void ThrowsOOB(Action action) + { + try + { + action(); + } + catch (IndexOutOfRangeException) + { + return; + } + throw new InvalidOperationException("IndexOutOfRangeException was expected"); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static void AssertEquals(T actual, T expected, [CallerLineNumber] int line = 0) + { + if (!actual.Equals(expected)) + { + throw new InvalidOperationException($"Line:{line} actual={actual}, expected={expected}"); + } + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static void Consume(T _) { } + + public record struct MyStruct(long a); +} diff --git a/src/tests/JIT/opt/ValueNumbering/ConstIndexRVA.csproj b/src/tests/JIT/opt/ValueNumbering/ConstIndexRVA.csproj new file mode 100644 index 00000000000000..6946bed81bfd5b --- /dev/null +++ b/src/tests/JIT/opt/ValueNumbering/ConstIndexRVA.csproj @@ -0,0 +1,9 @@ + + + Exe + True + + + + +