diff --git a/docs/design/datacontracts/Object.md b/docs/design/datacontracts/Object.md new file mode 100644 index 00000000000000..7e2f1cf4aff10e --- /dev/null +++ b/docs/design/datacontracts/Object.md @@ -0,0 +1,53 @@ +# Contract Object + +This contract is for getting information about well-known managed objects + +## APIs of contract + +``` csharp +// Get the method table address for the object +TargetPointer GetMethodTableAddress(TargetPointer address); + +// Get the string corresponding to a managed string object. Error if address does not represent a string. +string GetStringValue(TargetPointer address); +``` + +## Version 1 + +Data descriptors used: +| Data Descriptor Name | Field | Meaning | +| --- | --- | --- | +| `Object` | `m_pMethTab` | Method table for the object | +| `String` | `m_FirstChar` | First character of the string - `m_StringLength` can be used to read the full string (encoded in UTF-16) | +| `String` | `m_StringLength` | Length of the string in characters (encoded in UTF-16) | + +Global variables used: +| Global Name | Type | Purpose | +| --- | --- | --- | +| `ObjectToMethodTableUnmask` | uint8 | Bits to clear for converting to a method table address | +| `StringMethodTable` | TargetPointer | The method table for System.String | + +``` csharp +TargetPointer GetMethodTableAddress(TargetPointer address) +{ + TargetPointer mt = _targetPointer.ReadPointer(address + /* Object::m_pMethTab offset */); + return mt.Value & ~target.ReadGlobal("ObjectToMethodTableUnmask"); +} + +string GetStringValue(TargetPointer address) +{ + TargetPointer mt = GetMethodTableAddress(address); + TargetPointer stringMethodTable = target.ReadPointer(target.ReadGlobalPointer("StringMethodTable")); + if (mt != stringMethodTable) + throw new ArgumentException("Address does not represent a string object", nameof(address)); + + // Validates the method table + _ = target.Contracts.RuntimeTypeSystem.GetTypeHandle(mt); + + Data.String str = _target.ProcessedData.GetOrAdd(address); + uint length = target.Read(address + /* String::m_StringLength offset */); + Span span = stackalloc byte[(int)length * sizeof(char)]; + target.ReadBuffer(address + /* String::m_FirstChar offset */, span); + return new string(MemoryMarshal.Cast(span)); +} +``` diff --git a/src/coreclr/debug/daccess/dacimpl.h b/src/coreclr/debug/daccess/dacimpl.h index f3d12c3427263c..5b9f59575a6922 100644 --- a/src/coreclr/debug/daccess/dacimpl.h +++ b/src/coreclr/debug/daccess/dacimpl.h @@ -1241,6 +1241,7 @@ class ClrDataAccess HRESULT GetMethodTableForEEClassImpl (CLRDATA_ADDRESS eeClassReallyMT, CLRDATA_ADDRESS *value); HRESULT GetMethodTableNameImpl(CLRDATA_ADDRESS mt, unsigned int count, _Inout_updates_z_(count) WCHAR *mtName, unsigned int *pNeeded); HRESULT GetObjectExceptionDataImpl(CLRDATA_ADDRESS objAddr, struct DacpExceptionObjectData *data); + HRESULT GetObjectStringDataImpl(CLRDATA_ADDRESS obj, unsigned int count, _Inout_updates_z_(count) WCHAR *stringData, unsigned int *pNeeded); BOOL IsExceptionFromManagedCode(EXCEPTION_RECORD * pExceptionRecord); #ifndef TARGET_UNIX diff --git a/src/coreclr/debug/daccess/request.cpp b/src/coreclr/debug/daccess/request.cpp index 5c759faeac4f93..615c041210bbb2 100644 --- a/src/coreclr/debug/daccess/request.cpp +++ b/src/coreclr/debug/daccess/request.cpp @@ -1601,7 +1601,7 @@ ClrDataAccess::GetDomainFromContext(CLRDATA_ADDRESS contextAddr, CLRDATA_ADDRESS HRESULT -ClrDataAccess::GetObjectStringData(CLRDATA_ADDRESS obj, unsigned int count, _Inout_updates_z_(count) WCHAR *stringData, unsigned int *pNeeded) +ClrDataAccess::GetObjectStringData(CLRDATA_ADDRESS obj, unsigned int count, _Inout_updates_z_(count) WCHAR* stringData, unsigned int* pNeeded) { if (obj == 0) return E_INVALIDARG; @@ -1611,44 +1611,73 @@ ClrDataAccess::GetObjectStringData(CLRDATA_ADDRESS obj, unsigned int count, _Ino SOSDacEnter(); + if (m_cdacSos != NULL) + { + hr = m_cdacSos->GetObjectStringData(obj, count, stringData, pNeeded); + if (FAILED(hr)) + { + hr = GetObjectStringDataImpl(obj, count, stringData, pNeeded); + } +#ifdef _DEBUG + else + { + unsigned int neededLocal; + SString stringDataLocal; + HRESULT hrLocal = GetObjectStringDataImpl(obj, count, stringDataLocal.OpenUnicodeBuffer(count), &neededLocal); + _ASSERTE(hr == hrLocal); + _ASSERTE(pNeeded == NULL || *pNeeded == neededLocal); + _ASSERTE(u16_strncmp(stringData, stringDataLocal, count) == 0); + } +#endif + } + else + { + hr = GetObjectStringDataImpl(obj, count, stringData, pNeeded); + } + + SOSDacLeave(); + return hr; +} + +HRESULT +ClrDataAccess::GetObjectStringDataImpl(CLRDATA_ADDRESS obj, unsigned int count, _Inout_updates_z_(count) WCHAR *stringData, unsigned int *pNeeded) +{ TADDR mtTADDR = DACGetMethodTableFromObjectPointer(TO_TADDR(obj), m_pTarget); PTR_MethodTable mt = PTR_MethodTable(mtTADDR); // Object must be a string BOOL bFree = FALSE; if (!DacValidateMethodTable(mt, bFree)) - hr = E_INVALIDARG; - else if (HOST_CDADDR(mt) != HOST_CDADDR(g_pStringClass)) - hr = E_INVALIDARG; + return E_INVALIDARG; - if (SUCCEEDED(hr)) - { - PTR_StringObject str(TO_TADDR(obj)); - ULONG32 needed = (ULONG32)str->GetStringLength() + 1; + if (HOST_CDADDR(mt) != HOST_CDADDR(g_pStringClass)) + return E_INVALIDARG; - if (stringData && count > 0) - { - if (count > needed) - count = needed; + PTR_StringObject str(TO_TADDR(obj)); + ULONG32 needed = (ULONG32)str->GetStringLength() + 1; - TADDR pszStr = TO_TADDR(obj)+offsetof(StringObject, m_FirstChar); - hr = m_pTarget->ReadVirtual(pszStr, (PBYTE)stringData, count * sizeof(WCHAR), &needed); + HRESULT hr; + if (stringData && count > 0) + { + if (count > needed) + count = needed; - if (SUCCEEDED(hr)) - stringData[count - 1] = W('\0'); - else - stringData[0] = W('\0'); - } - else - { - hr = E_INVALIDARG; - } + TADDR pszStr = TO_TADDR(obj)+offsetof(StringObject, m_FirstChar); + hr = m_pTarget->ReadVirtual(pszStr, (PBYTE)stringData, count * sizeof(WCHAR), &needed); - if (pNeeded) - *pNeeded = needed; + if (SUCCEEDED(hr)) + stringData[count - 1] = W('\0'); + else + stringData[0] = W('\0'); + } + else + { + hr = E_INVALIDARG; } - SOSDacLeave(); + if (pNeeded) + *pNeeded = needed; + return hr; } diff --git a/src/coreclr/debug/runtimeinfo/contracts.jsonc b/src/coreclr/debug/runtimeinfo/contracts.jsonc index 7d477b00cd858d..7ccabcb41dc75d 100644 --- a/src/coreclr/debug/runtimeinfo/contracts.jsonc +++ b/src/coreclr/debug/runtimeinfo/contracts.jsonc @@ -12,6 +12,7 @@ "DacStreams": 1, "Exception": 1, "Loader": 1, + "Object": 1, "RuntimeTypeSystem": 1, "Thread": 1 } diff --git a/src/coreclr/debug/runtimeinfo/datadescriptor.h b/src/coreclr/debug/runtimeinfo/datadescriptor.h index da83bebcfea39f..f51ddd2745fb0c 100644 --- a/src/coreclr/debug/runtimeinfo/datadescriptor.h +++ b/src/coreclr/debug/runtimeinfo/datadescriptor.h @@ -172,6 +172,17 @@ CDAC_TYPE_BEGIN(GCHandle) CDAC_TYPE_SIZE(sizeof(OBJECTHANDLE)) CDAC_TYPE_END(GCHandle) +CDAC_TYPE_BEGIN(Object) +CDAC_TYPE_INDETERMINATE(Object) +CDAC_TYPE_FIELD(Object, /*pointer*/, m_pMethTab, cdac_offsets::m_pMethTab) +CDAC_TYPE_END(Object) + +CDAC_TYPE_BEGIN(String) +CDAC_TYPE_INDETERMINATE(String) +CDAC_TYPE_FIELD(String, /*pointer*/, m_FirstChar, cdac_offsets::m_FirstChar) +CDAC_TYPE_FIELD(String, /*uint32*/, m_StringLength, cdac_offsets::m_StringLength) +CDAC_TYPE_END(String) + // Loader CDAC_TYPE_BEGIN(Module) @@ -264,8 +275,15 @@ CDAC_GLOBAL(FeatureEHFunclets, uint8, 1) #else CDAC_GLOBAL(FeatureEHFunclets, uint8, 0) #endif +// See Object::GetGCSafeMethodTable +#ifdef TARGET_64BIT +CDAC_GLOBAL(ObjectToMethodTableUnmask, uint8, 1 | 1 << 1 | 1 << 2) +#else +CDAC_GLOBAL(ObjectToMethodTableUnmask, uint8, 1 | 1 << 1) +#endif //TARGET_64BIT CDAC_GLOBAL(SOSBreakingChangeVersion, uint8, SOS_BREAKING_CHANGE_VERSION) CDAC_GLOBAL_POINTER(FreeObjectMethodTable, &::g_pFreeObjectMethodTable) +CDAC_GLOBAL_POINTER(StringMethodTable, &::g_pStringClass) CDAC_GLOBAL_POINTER(MiniMetaDataBuffAddress, &::g_MiniMetaDataBuffAddress) CDAC_GLOBAL_POINTER(MiniMetaDataBuffMaxSize, &::g_MiniMetaDataBuffMaxSize) CDAC_GLOBALS_END() diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index 0e63471fbdeb5c..ce8cfe9f57f0d6 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -4514,6 +4514,7 @@ class Compiler GenTree* impStoreNullableFields(CORINFO_CLASS_HANDLE nullableCls, GenTree* value); + GenTree* impInlineUnboxNullable(CORINFO_CLASS_HANDLE nullableCls, GenTree* nullableClsNode, GenTree* obj); void impLoadNullableFields(GenTree* nullableObj, CORINFO_CLASS_HANDLE nullableCls, GenTree** hasValueFld, GenTree** valueFld); int impBoxPatternMatch(CORINFO_RESOLVED_TOKEN* pResolvedToken, @@ -7649,6 +7650,8 @@ class Compiler LoopLocalOccurrences* loopLocals); bool optCanAndShouldChangeExitTest(GenTree* cond, bool dump); bool optPrimaryIVHasNonLoopUses(unsigned lclNum, FlowGraphNaturalLoop* loop, LoopLocalOccurrences* loopLocals); + + bool optWidenIVs(ScalarEvolutionContext& scevContext, FlowGraphNaturalLoop* loop, LoopLocalOccurrences* loopLocals); bool optWidenPrimaryIV(FlowGraphNaturalLoop* loop, unsigned lclNum, ScevAddRec* addRec, @@ -7665,6 +7668,9 @@ class Compiler void optReplaceWidenedIV(unsigned lclNum, unsigned ssaNum, unsigned newLclNum, Statement* stmt); void optSinkWidenedIV(unsigned lclNum, unsigned newLclNum, FlowGraphNaturalLoop* loop); + bool optRemoveUnusedIVs(FlowGraphNaturalLoop* loop, LoopLocalOccurrences* loopLocals); + bool optIsUpdateOfIVWithoutSideEffects(GenTree* tree, unsigned lclNum); + // Redundant branch opts // PhaseStatus optRedundantBranches(); diff --git a/src/coreclr/jit/importer.cpp b/src/coreclr/jit/importer.cpp index d1c11b9f65fc80..85682710ea1992 100644 --- a/src/coreclr/jit/importer.cpp +++ b/src/coreclr/jit/importer.cpp @@ -2868,6 +2868,117 @@ GenTree* Compiler::impImportLdvirtftn(GenTree* thisPtr, return call; } +//------------------------------------------------------------------------ +// impInlineUnboxNullable: Generate code for unboxing Nullable from an object (obj) +// We either inline the unbox operation (if profitable) or call the helper. +// The inline expansion is as follows: +// +// Nullable result; +// if (obj == null) +// { +// result = default; +// } +// else if (obj->pMT == ) +// { +// result._hasValue = true; +// result._value = *(T*)(obj + sizeof(void*)); +// } +// else +// { +// result = CORINFO_HELP_UNBOX_NULLABLE(&result, nullableCls, obj); +// } +// +// Arguments: +// nullableCls - class handle representing the Nullable type +// nullableClsNode - tree node representing the Nullable type (can be a runtime lookup tree) +// obj - object to unbox +// +// Return Value: +// A local node representing the unboxed value (Nullable) +// +GenTree* Compiler::impInlineUnboxNullable(CORINFO_CLASS_HANDLE nullableCls, GenTree* nullableClsNode, GenTree* obj) +{ + assert(info.compCompHnd->isNullableType(nullableCls) == TypeCompareState::Must); + + unsigned resultTmp = lvaGrabTemp(true DEBUGARG("Nullable tmp")); + lvaSetStruct(resultTmp, nullableCls, false); + lvaGetDesc(resultTmp)->lvHasLdAddrOp = true; + GenTreeLclFld* resultAddr = gtNewLclAddrNode(resultTmp, 0); + + // Check profitability of inlining the unbox operation + bool shouldExpandInline = !compCurBB->isRunRarely() && opts.OptimizationEnabled() && !eeIsSharedInst(nullableCls); + + // It's less profitable to inline the unbox operation if the underlying type is too large + CORINFO_CLASS_HANDLE unboxType = NO_CLASS_HANDLE; + if (shouldExpandInline) + { + // The underlying type of the nullable: + unboxType = info.compCompHnd->getTypeForBox(nullableCls); + shouldExpandInline = info.compCompHnd->getClassSize(unboxType) <= getUnrollThreshold(Memcpy); + } + + if (!shouldExpandInline) + { + // No expansion needed, just call the helper + GenTreeCall* call = + gtNewHelperCallNode(CORINFO_HELP_UNBOX_NULLABLE, TYP_VOID, resultAddr, nullableClsNode, obj); + impAppendTree(call, CHECK_SPILL_ALL, impCurStmtDI); + return gtNewLclvNode(resultTmp, TYP_STRUCT); + } + + // Clone the object (and spill side effects) + GenTree* objClone; + obj = impCloneExpr(obj, &objClone, CHECK_SPILL_ALL, nullptr DEBUGARG("op1 spilled for Nullable unbox")); + + // Unbox the object to the result local: + // + // result._hasValue = true; + // result._value = MethodTableLookup(obj); + // + CORINFO_FIELD_HANDLE valueFldHnd = info.compCompHnd->getFieldInClass(nullableCls, 1); + CORINFO_CLASS_HANDLE valueStructCls; + var_types valueType = JITtype2varType(info.compCompHnd->getFieldType(valueFldHnd, &valueStructCls)); + static_assert_no_msg(OFFSETOF__CORINFO_NullableOfT__hasValue == 0); + unsigned hasValOffset = OFFSETOF__CORINFO_NullableOfT__hasValue; + unsigned valueOffset = info.compCompHnd->getFieldOffset(valueFldHnd); + + GenTree* boxedContentAddr = + gtNewOperNode(GT_ADD, TYP_BYREF, gtCloneExpr(objClone), gtNewIconNode(TARGET_POINTER_SIZE, TYP_I_IMPL)); + // Load the boxed content from the object (op1): + GenTree* boxedContent = valueType == TYP_STRUCT ? gtNewBlkIndir(typGetObjLayout(valueStructCls), boxedContentAddr) + : gtNewIndir(valueType, boxedContentAddr); + // Now do two stores via a comma: + GenTree* setHasValue = gtNewStoreLclFldNode(resultTmp, TYP_UBYTE, hasValOffset, gtNewIconNode(1)); + GenTree* setValue = gtNewStoreLclFldNode(resultTmp, valueType, valueOffset, boxedContent); + GenTree* unboxTree = gtNewOperNode(GT_COMMA, TYP_VOID, setHasValue, setValue); + + // Fallback helper call + // TODO: Mark as no-return when appropriate + GenTreeCall* helperCall = + gtNewHelperCallNode(CORINFO_HELP_UNBOX_NULLABLE, TYP_VOID, resultAddr, nullableClsNode, gtCloneExpr(objClone)); + + // Nested QMARK - "obj->pMT == ? unboxTree : helperCall" + assert(unboxType != NO_CLASS_HANDLE); + GenTree* unboxTypeNode = gtNewIconEmbClsHndNode(unboxType); + GenTree* objMT = gtNewMethodTableLookup(objClone); + GenTree* mtLookupCond = gtNewOperNode(GT_NE, TYP_INT, objMT, unboxTypeNode); + GenTreeColon* mtCheckColon = gtNewColonNode(TYP_VOID, helperCall, unboxTree); + GenTreeQmark* mtCheckQmark = gtNewQmarkNode(TYP_VOID, mtLookupCond, mtCheckColon); + mtCheckQmark->SetThenNodeLikelihood(0); + + // Zero initialize the result in case of "obj == null" + GenTreeLclVar* zeroInitResultNode = gtNewStoreLclVarNode(resultTmp, gtNewIconNode(0)); + + // Root condition - "obj == null ? zeroInitResultNode : mtCheckQmark" + GenTree* nullcheck = gtNewOperNode(GT_NE, TYP_INT, obj, gtNewNull()); + GenTreeColon* nullCheckColon = gtNewColonNode(TYP_VOID, mtCheckQmark, zeroInitResultNode); + GenTreeQmark* nullCheckQmark = gtNewQmarkNode(TYP_VOID, nullcheck, nullCheckColon); + + // Spill the root QMARK and return the result local + impAppendTree(nullCheckQmark, CHECK_SPILL_ALL, impCurStmtDI); + return gtNewLclvNode(resultTmp, TYP_STRUCT); +} + //------------------------------------------------------------------------ // impStoreNullableFields: create a Nullable object and store // 'hasValue' (always true) and the given value for 'value' field @@ -10134,36 +10245,20 @@ void Compiler::impImportBlockCode(BasicBlock* block) op2 = gtNewIconNode(TARGET_POINTER_SIZE, TYP_I_IMPL); op1 = gtNewOperNode(GT_ADD, TYP_BYREF, cloneOperand, op2); } + else if (helper == CORINFO_HELP_UNBOX_NULLABLE) + { + // op1 is the object being unboxed + // op2 is either a class handle node or a runtime lookup node (it's fine to reorder) + op1 = impInlineUnboxNullable(resolvedToken.hClass, op2, op1); + } else { // Don't optimize, just call the helper and be done with it JITDUMP("\n Importing %s as helper call because %s\n", opcode == CEE_UNBOX ? "UNBOX" : "UNBOX.ANY", canExpandInline ? "want smaller code or faster jitting" : "inline expansion not legal"); - if (helper == CORINFO_HELP_UNBOX) - { - op1 = gtNewHelperCallNode(helper, TYP_BYREF, op2, op1); - } - else - { - assert(helper == CORINFO_HELP_UNBOX_NULLABLE); - - // We're going to emit the following sequence of IR: - // - // Nullable result; - // void CORINFO_HELP_UNBOX_NULLABLE(&result, unboxCls, obj); - // push result; - // - unsigned resultTmp = lvaGrabTemp(true DEBUGARG("Nullable tmp")); - lvaSetStruct(resultTmp, resolvedToken.hClass, false); - lvaGetDesc(resultTmp)->lvHasLdAddrOp = true; - - GenTreeLclFld* resultAddr = gtNewLclAddrNode(resultTmp, 0); - // NOTE: it's fine for op2 to be evaluated before op1 - GenTreeCall* helperCall = gtNewHelperCallNode(helper, TYP_VOID, resultAddr, op2, op1); - impAppendTree(helperCall, CHECK_SPILL_ALL, impCurStmtDI); - op1 = gtNewLclvNode(resultTmp, TYP_STRUCT); - } + assert(helper == CORINFO_HELP_UNBOX); + op1 = gtNewHelperCallNode(helper, TYP_BYREF, op2, op1); } assert((helper == CORINFO_HELP_UNBOX && op1->gtType == TYP_BYREF) || // Unbox helper returns a byref. diff --git a/src/coreclr/jit/inductionvariableopts.cpp b/src/coreclr/jit/inductionvariableopts.cpp index 9ae98dfbf7770d..32c5a1967b6b05 100644 --- a/src/coreclr/jit/inductionvariableopts.cpp +++ b/src/coreclr/jit/inductionvariableopts.cpp @@ -751,6 +751,77 @@ void Compiler::optBestEffortReplaceNarrowIVUses( }); } +//------------------------------------------------------------------------ +// optWidenIVs: Widen IVs of the specified loop. +// +// Parameters: +// scevContext - Context for scalar evolution +// loop - The loop +// loopLocals - Data structure for locals occurrences +// +// Returns: +// True if any primary IV was widened. +// +bool Compiler::optWidenIVs(ScalarEvolutionContext& scevContext, + FlowGraphNaturalLoop* loop, + LoopLocalOccurrences* loopLocals) +{ + JITDUMP("Considering primary IVs of " FMT_LP " for widening\n", loop->GetIndex()); + + unsigned numWidened = 0; + for (Statement* stmt : loop->GetHeader()->Statements()) + { + if (!stmt->IsPhiDefnStmt()) + { + break; + } + + JITDUMP("\n"); + DISPSTMT(stmt); + + Scev* scev = scevContext.Analyze(loop->GetHeader(), stmt->GetRootNode()); + if (scev == nullptr) + { + JITDUMP(" Could not analyze header PHI\n"); + continue; + } + + JITDUMP(" => "); + DBEXEC(verbose, scev->Dump(this)); + JITDUMP("\n"); + if (!scev->OperIs(ScevOper::AddRec)) + { + JITDUMP(" Not an addrec\n"); + continue; + } + + ScevAddRec* addRec = (ScevAddRec*)scev; + + unsigned lclNum = stmt->GetRootNode()->AsLclVarCommon()->GetLclNum(); + LclVarDsc* lclDsc = lvaGetDesc(lclNum); + JITDUMP(" V%02u is a primary induction variable in " FMT_LP "\n", lclNum, loop->GetIndex()); + + assert(!lclDsc->lvPromoted); + + // For a struct field with occurrences of the parent local we won't + // be able to do much. + if (lclDsc->lvIsStructField && loopLocals->HasAnyOccurrences(loop, lclDsc->lvParentLcl)) + { + JITDUMP(" V%02u is a struct field whose parent local V%02u has occurrences inside the loop\n", lclNum, + lclDsc->lvParentLcl); + continue; + } + + if (optWidenPrimaryIV(loop, lclNum, addRec, loopLocals)) + { + numWidened++; + } + } + + Metrics.WidenedIVs += numWidened; + return numWidened > 0; +} + //------------------------------------------------------------------------ // optWidenPrimaryIV: Attempt to widen a primary IV. // @@ -903,6 +974,7 @@ bool Compiler::optWidenPrimaryIV(FlowGraphNaturalLoop* loop, loopLocals->VisitStatementsWithOccurrences(loop, lclNum, replace); optSinkWidenedIV(lclNum, newLclNum, loop); + loopLocals->Invalidate(loop); return true; } @@ -1036,29 +1108,12 @@ bool Compiler::optMakeExitTestDownwardsCounted(ScalarEvolutionContext& scevConte return true; } - GenTree* rootNode = stmt->GetRootNode(); - if (!rootNode->OperIsLocalStore()) + if (optIsUpdateOfIVWithoutSideEffects(stmt->GetRootNode(), candidateLclNum)) { - // Cannot reason about this use of the local, cannot remove - // TODO-CQ: In some cases it may be profitable to compute the - // value in terms of the down-counting IV. - return false; - } - - if (rootNode->AsLclVarCommon()->GetLclNum() != candidateLclNum) - { - // Used to compute a value stored to some other local, cannot remove - return false; - } - - if ((rootNode->AsLclVarCommon()->Data()->gtFlags & GTF_SIDE_EFFECT) != 0) - { - // May be used inside the data node for something that has side effects, cannot remove - return false; + return true; } - // Can remove this store - return true; + return false; }; if (!loopLocals->VisitStatementsWithOccurrences(loop, candidateLclNum, checkRemovableUse)) @@ -1152,26 +1207,9 @@ bool Compiler::optMakeExitTestDownwardsCounted(ScalarEvolutionContext& scevConte JITDUMP("\n Updated exit test:\n"); DISPSTMT(jtrueStmt); - - JITDUMP("\n Now removing uses of old IVs\n"); - - for (int i = 0; i < removableLocals.Height(); i++) - { - unsigned removableLcl = removableLocals.Bottom(i); - JITDUMP(" Removing uses of V%02u\n", removableLcl); - auto deleteStatement = [=](BasicBlock* block, Statement* stmt) { - if (stmt != jtrueStmt) - { - fgRemoveStmt(block, stmt); - } - - return true; - }; - - loopLocals->VisitStatementsWithOccurrences(loop, removableLcl, deleteStatement); - } - JITDUMP("\n"); + + loopLocals->Invalidate(loop); return true; } @@ -1627,18 +1665,10 @@ bool StrengthReductionContext::InitializeCursors(GenTreeLclVarCommon* primaryIVL bool StrengthReductionContext::IsUseExpectedToBeRemoved(BasicBlock* block, Statement* stmt, GenTreeLclVarCommon* tree) { unsigned primaryIVLclNum = tree->GetLclNum(); - if (stmt->GetRootNode()->OperIsLocalStore()) + if (m_comp->optIsUpdateOfIVWithoutSideEffects(stmt->GetRootNode(), tree->GetLclNum())) { - GenTreeLclVarCommon* lcl = stmt->GetRootNode()->AsLclVarCommon(); - if ((lcl->GetLclNum() == primaryIVLclNum) && ((lcl->Data()->gtFlags & GTF_SIDE_EFFECT) == 0)) - { - // Store to the primary IV without side effects; if we end - // up strength reducing, then this store is expected to be - // removed by making the loop downwards counted. - return true; - } - - return false; + // Removal of unused IVs will get rid of this. + return true; } bool isInsideExitTest = @@ -1819,17 +1849,18 @@ bool StrengthReductionContext::StaysWithinManagedObject(ArrayStack* Scev* baseScev = addRec->Start->PeelAdditions(&offset); offset = static_cast(offset); - // We only support arrays here. To strength reduce Span accesses we need - // additional properies on the range designated by a Span that we - // currently do not specify, or we need to prove that the byref we may form - // in the IV update would have been formed anyway by the loop. + // We only support arrays and strings here. To strength reduce Span + // accesses we need additional properies on the range designated by a + // Span that we currently do not specify, or we need to prove that the + // byref we may form in the IV update would have been formed anyway by the + // loop. if (!baseScev->OperIs(ScevOper::Local) || !baseScev->TypeIs(TYP_REF)) { return false; } - // Now use the fact that we keep ARR_ADDRs in the IR when we have array - // accesses. + // Now use the fact that we keep ARR_ADDRs in the IR when we have + // array/string accesses. GenTreeArrAddr* arrAddr = nullptr; for (int i = 0; i < cursors->Height(); i++) { @@ -1860,7 +1891,7 @@ bool StrengthReductionContext::StaysWithinManagedObject(ArrayStack* ScevLocal* local = (ScevLocal*)baseScev; ValueNumPair vnp = m_scevContext.MaterializeVN(baseScev); - if (vnp.GetConservative() == ValueNumStore::NoVN) + if (!vnp.BothDefined()) { return false; } @@ -1871,32 +1902,49 @@ bool StrengthReductionContext::StaysWithinManagedObject(ArrayStack* return false; } - // We have a non-null array. Check that the 'start' offset looks fine. - // TODO: We could also use assertions on the length of the array. E.g. if - // we know the length of the array is > 3, then we can allow the add rec to - // have a later start. Maybe range check can be used? - if ((offset < 0) || (offset > (int64_t)OFFSETOF__CORINFO_Array__data)) + // We have a non-null array/string. Check that the 'start' offset looks + // fine. TODO: We could also use assertions on the length of the + // array/string. E.g. if we know the length of the array is > 3, then we + // can allow the add rec to have a later start. Maybe range check can be + // used? + if ((offset < 0) || (offset > arrAddr->GetFirstElemOffset())) { return false; } - // Now see if we have a bound that guarantees that we iterate less than the - // array length's times. + // Now see if we have a bound that guarantees that we iterate fewer times + // than the array/string's length. + ValueNum arrLengthVN = m_comp->vnStore->VNForFunc(TYP_INT, VNF_ARR_LENGTH, vnp.GetLiberal()); + for (int i = 0; i < m_backEdgeBounds.Height(); i++) { - // TODO: EvaluateRelop ought to be powerful enough to prove something - // like bound < ARR_LENGTH(vn), but it is not able to prove that - // currently, even for bound = ARR_LENGTH(vn) - 1 (common case). Scev* bound = m_backEdgeBounds.Bottom(i); + if (!bound->TypeIs(TYP_INT)) + { + // Currently cannot handle bounds that aren't 32 bit. + continue; + } - int64_t boundOffset; - Scev* boundBase = bound->PeelAdditions(&boundOffset); - - if (bound->TypeIs(TYP_INT)) + // In some cases VN is powerful enough to evaluate bound < + // ARR_LENGTH(vn) directly. + ValueNumPair boundVN = m_scevContext.MaterializeVN(bound); + if (boundVN.GetLiberal() != ValueNumStore::NoVN) { - boundOffset = static_cast(boundOffset); + ValueNum relop = m_comp->vnStore->VNForFunc(TYP_INT, VNF_LT_UN, boundVN.GetLiberal(), arrLengthVN); + if (m_scevContext.EvaluateRelop(relop) == RelopEvaluationResult::True) + { + return true; + } } + // In common cases VN cannot prove the above, so try a little bit + // harder. In this case we know the bound doesn't overflow + // (conceptually the bound is an arbitrary precision integer and a + // negative bound does not make sense). + int64_t boundOffset; + Scev* boundBase = bound->PeelAdditions(&boundOffset); + + boundOffset = static_cast(boundOffset); if (boundOffset >= 0) { // If we take the backedge >= the array length times, then we would @@ -1904,20 +1952,18 @@ bool StrengthReductionContext::StaysWithinManagedObject(ArrayStack* continue; } + // Now try to prove that boundBase <= ARR_LENGTH(vn). Commonly + // boundBase == ARR_LENGTH(vn) exactly, but it may also have a + // dominating subsuming compare before the loop due to loop cloning. ValueNumPair boundBaseVN = m_scevContext.MaterializeVN(boundBase); - - VNFuncApp vnf; - if (!m_comp->vnStore->GetVNFunc(boundBaseVN.GetConservative(), &vnf)) - { - continue; - } - - if ((vnf.m_func != VNF_ARR_LENGTH) || (vnf.m_args[0] != vnp.GetConservative())) + if (boundBaseVN.GetLiberal() != ValueNumStore::NoVN) { - continue; + ValueNum relop = m_comp->vnStore->VNForFunc(TYP_INT, VNF_LE, boundBaseVN.GetLiberal(), arrLengthVN); + if (m_scevContext.EvaluateRelop(relop) == RelopEvaluationResult::True) + { + return true; + } } - - return true; } return false; @@ -2092,6 +2138,92 @@ BasicBlock* StrengthReductionContext::FindUpdateInsertionPoint(ArrayStackGetHeader()->Statements()) + { + if (!stmt->IsPhiDefnStmt()) + { + break; + } + + unsigned lclNum = stmt->GetRootNode()->AsLclVarCommon()->GetLclNum(); + JITDUMP(" V%02u", lclNum); + if (optPrimaryIVHasNonLoopUses(lclNum, loop, loopLocals)) + { + JITDUMP(" has non-loop uses, cannot remove\n"); + continue; + } + + auto visit = [=](BasicBlock* block, Statement* stmt) { + return optIsUpdateOfIVWithoutSideEffects(stmt->GetRootNode(), lclNum); + }; + + if (!loopLocals->VisitStatementsWithOccurrences(loop, lclNum, visit)) + { + JITDUMP(" has essential uses, cannot remove\n"); + continue; + } + + JITDUMP(" has no essential uses and will be removed\n", lclNum); + auto remove = [=](BasicBlock* block, Statement* stmt) { + JITDUMP(" Removing " FMT_STMT "\n", stmt->GetID()); + fgRemoveStmt(block, stmt); + return true; + }; + + loopLocals->VisitStatementsWithOccurrences(loop, lclNum, remove); + numRemoved++; + loopLocals->Invalidate(loop); + } + + Metrics.UnusedIVsRemoved += numRemoved; + return numRemoved > 0; +} + +//------------------------------------------------------------------------ +// optIsUpdateOfIVWithoutSideEffects: Check if a tree is an update of a +// specific local with no other side effects. +// +// Returns: +// True if so. +// +bool Compiler::optIsUpdateOfIVWithoutSideEffects(GenTree* tree, unsigned lclNum) +{ + if (!tree->OperIsLocalStore()) + { + return false; + } + + GenTreeLclVarCommon* store = tree->AsLclVarCommon(); + if (store->GetLclNum() != lclNum) + { + // Store that uses the local as a source; this primary IV is used + return false; + } + + if ((store->Data()->gtFlags & GTF_SIDE_EFFECT) != 0) + { + // Primary IV may be used inside a side effect + return false; + } + + return true; +} + //------------------------------------------------------------------------ // optInductionVariables: Try and optimize induction variables in the method. // @@ -2162,66 +2294,17 @@ PhaseStatus Compiler::optInductionVariables() // addressing modes can include the zero/sign-extension of the index // for free. #if defined(TARGET_XARCH) && defined(TARGET_64BIT) - int numWidened = 0; - - JITDUMP("Considering primary IVs of " FMT_LP " for widening\n", loop->GetIndex()); - - for (Statement* stmt : loop->GetHeader()->Statements()) + if (optWidenIVs(scevContext, loop, &loopLocals)) { - if (!stmt->IsPhiDefnStmt()) - { - break; - } - - JITDUMP("\n"); - DISPSTMT(stmt); - - Scev* scev = scevContext.Analyze(loop->GetHeader(), stmt->GetRootNode()); - if (scev == nullptr) - { - JITDUMP(" Could not analyze header PHI\n"); - continue; - } - - JITDUMP(" => "); - DBEXEC(verbose, scev->Dump(this)); - JITDUMP("\n"); - if (!scev->OperIs(ScevOper::AddRec)) - { - JITDUMP(" Not an addrec\n"); - continue; - } - - ScevAddRec* addRec = (ScevAddRec*)scev; - - unsigned lclNum = stmt->GetRootNode()->AsLclVarCommon()->GetLclNum(); - LclVarDsc* lclDsc = lvaGetDesc(lclNum); - JITDUMP(" V%02u is a primary induction variable in " FMT_LP "\n", lclNum, loop->GetIndex()); - - assert(!lclDsc->lvPromoted); - - // For a struct field with occurrences of the parent local we won't - // be able to do much. - if (lclDsc->lvIsStructField && loopLocals.HasAnyOccurrences(loop, lclDsc->lvParentLcl)) - { - JITDUMP(" V%02u is a struct field whose parent local V%02u has occurrences inside the loop\n", lclNum, - lclDsc->lvParentLcl); - continue; - } - - if (optWidenPrimaryIV(loop, lclNum, addRec, &loopLocals)) - { - numWidened++; - changed = true; - } + Metrics.LoopsIVWidened++; + changed = true; } +#endif - Metrics.WidenedIVs += numWidened; - if (numWidened > 0) + if (optRemoveUnusedIVs(loop, &loopLocals)) { - Metrics.LoopsIVWidened++; + changed = true; } -#endif } fgInvalidateDfsTree(); diff --git a/src/coreclr/jit/jitmetadatalist.h b/src/coreclr/jit/jitmetadatalist.h index 4aad9abd1b3ccb..51ef3c81044649 100644 --- a/src/coreclr/jit/jitmetadatalist.h +++ b/src/coreclr/jit/jitmetadatalist.h @@ -35,6 +35,7 @@ JITMETADATAMETRIC(LoopAlignmentCandidates, int, 0) JITMETADATAMETRIC(LoopsAligned, int, 0) JITMETADATAMETRIC(LoopsIVWidened, int, 0) JITMETADATAMETRIC(WidenedIVs, int, 0) +JITMETADATAMETRIC(UnusedIVsRemoved, int, 0) JITMETADATAMETRIC(LoopsMadeDownwardsCounted, int, 0) JITMETADATAMETRIC(LoopsStrengthReduced, int, 0) JITMETADATAMETRIC(VarsInSsa, int, 0) diff --git a/src/coreclr/jit/scev.cpp b/src/coreclr/jit/scev.cpp index ec7d8febb31507..540d5bed9f0e7f 100644 --- a/src/coreclr/jit/scev.cpp +++ b/src/coreclr/jit/scev.cpp @@ -1501,16 +1501,13 @@ RelopEvaluationResult ScalarEvolutionContext::EvaluateRelop(ValueNum vn) } // Evaluate by using dominators and RBO's logic. + assert(m_comp->m_domTree != nullptr); // // TODO-CQ: Using assertions could be stronger given its dataflow, but it // is not convenient to use (optVNConstantPropOnJTrue does not actually // make any use of assertions to evaluate conditionals, so it seems like // the logic does not actually exist anywhere.) // - if (m_comp->m_domTree == nullptr) - { - m_comp->m_domTree = FlowGraphDominatorTree::Build(m_comp->m_dfsTree); - } for (BasicBlock* idom = m_loop->GetHeader()->bbIDom; idom != nullptr; idom = idom->bbIDom) { diff --git a/src/coreclr/jit/scev.h b/src/coreclr/jit/scev.h index 1714d1d3fc3152..654a450482086a 100644 --- a/src/coreclr/jit/scev.h +++ b/src/coreclr/jit/scev.h @@ -239,10 +239,9 @@ class ScalarEvolutionContext Scev* CreateScevForConstant(GenTreeIntConCommon* tree); void ExtractAddOperands(ScevBinop* add, ArrayStack& operands); - VNFunc MapRelopToVNFunc(genTreeOps oper, bool isUnsigned); - RelopEvaluationResult EvaluateRelop(ValueNum relop); - bool MayOverflowBeforeExit(ScevAddRec* lhs, Scev* rhs, VNFunc exitOp); - bool AddRecMayOverflow(ScevAddRec* addRec, bool signedBound, const SimplificationAssumptions& assumptions); + VNFunc MapRelopToVNFunc(genTreeOps oper, bool isUnsigned); + bool MayOverflowBeforeExit(ScevAddRec* lhs, Scev* rhs, VNFunc exitOp); + bool AddRecMayOverflow(ScevAddRec* addRec, bool signedBound, const SimplificationAssumptions& assumptions); bool Materialize(Scev* scev, bool createIR, GenTree** result, ValueNumPair* resultVN); @@ -262,7 +261,8 @@ class ScalarEvolutionContext static const SimplificationAssumptions NoAssumptions; Scev* Simplify(Scev* scev, const SimplificationAssumptions& assumptions = NoAssumptions); - Scev* ComputeExitNotTakenCount(BasicBlock* exiting); + Scev* ComputeExitNotTakenCount(BasicBlock* exiting); + RelopEvaluationResult EvaluateRelop(ValueNum relop); GenTree* Materialize(Scev* scev); ValueNumPair MaterializeVN(Scev* scev); diff --git a/src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.Unix.targets b/src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.Unix.targets index 07fba4c26ee897..e9cdbde08e5fd8 100644 --- a/src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.Unix.targets +++ b/src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.Unix.targets @@ -141,9 +141,6 @@ The .NET Foundation licenses this file to you under the MIT license. - - - diff --git a/src/coreclr/vm/object.h b/src/coreclr/vm/object.h index 4b9df2e18275f1..222c729f7dce41 100644 --- a/src/coreclr/vm/object.h +++ b/src/coreclr/vm/object.h @@ -462,6 +462,14 @@ class Object private: VOID ValidateInner(BOOL bDeep, BOOL bVerifyNextHeader, BOOL bVerifySyncBlock); + + template friend struct ::cdac_offsets; +}; + +template<> +struct cdac_offsets +{ + static constexpr size_t m_pMethTab = offsetof(Object, m_pMethTab); }; /* @@ -930,6 +938,15 @@ class StringObject : public Object private: static STRINGREF* EmptyStringRefPtr; static bool EmptyStringIsFrozen; + + template friend struct ::cdac_offsets; +}; + +template<> +struct cdac_offsets +{ + static constexpr size_t m_FirstChar = offsetof(StringObject, m_FirstChar); + static constexpr size_t m_StringLength = offsetof(StringObject, m_StringLength); }; /*================================GetEmptyString================================ diff --git a/src/libraries/System.Net.NameResolution/src/System/Net/Dns.cs b/src/libraries/System.Net.NameResolution/src/System/Net/Dns.cs index b592f0ce44857f..c8a526ccc4b3cc 100644 --- a/src/libraries/System.Net.NameResolution/src/System/Net/Dns.cs +++ b/src/libraries/System.Net.NameResolution/src/System/Net/Dns.cs @@ -703,9 +703,9 @@ private static Task RunAsync(Func>)s_tasks).Remove(new KeyValuePair(key!, task)); - // Since it was canceled, func(..) had not executed and call AfterResolution it needs to be called here. - NameResolutionTelemetry.Log.AfterResolution(key!, activity, new OperationCanceledException()); } + // Since it was canceled, func(..) had not executed and call AfterResolution it needs to be called here. + NameResolutionTelemetry.Log.AfterResolution(key!, activity, new OperationCanceledException()); }, key, CancellationToken.None, TaskContinuationOptions.OnlyOnCanceled | TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default); } diff --git a/src/mono/mono/metadata/marshal.c b/src/mono/mono/metadata/marshal.c index bff7eb1d3e3ad4..96b8a5fe96de05 100644 --- a/src/mono/mono/metadata/marshal.c +++ b/src/mono/mono/metadata/marshal.c @@ -130,8 +130,9 @@ static GENERATE_TRY_GET_CLASS_WITH_CACHE (suppress_gc_transition_attribute, "Sys static GENERATE_TRY_GET_CLASS_WITH_CACHE (unmanaged_callers_only_attribute, "System.Runtime.InteropServices", "UnmanagedCallersOnlyAttribute") static GENERATE_TRY_GET_CLASS_WITH_CACHE (unmanaged_callconv_attribute, "System.Runtime.InteropServices", "UnmanagedCallConvAttribute") -GENERATE_TRY_GET_CLASS_WITH_CACHE (swift_error, "System.Runtime.InteropServices.Swift", "SwiftError") GENERATE_TRY_GET_CLASS_WITH_CACHE (swift_self, "System.Runtime.InteropServices.Swift", "SwiftSelf") +GENERATE_TRY_GET_CLASS_WITH_CACHE (swift_self_t, "System.Runtime.InteropServices.Swift", "SwiftSelf`1"); +GENERATE_TRY_GET_CLASS_WITH_CACHE (swift_error, "System.Runtime.InteropServices.Swift", "SwiftError") GENERATE_TRY_GET_CLASS_WITH_CACHE (swift_indirect_result, "System.Runtime.InteropServices.Swift", "SwiftIndirectResult") static gboolean type_is_blittable (MonoType *type); @@ -3698,12 +3699,14 @@ mono_marshal_get_native_wrapper (MonoMethod *method, gboolean check_exceptions, if (mono_method_signature_has_ext_callconv (csig, MONO_EXT_CALLCONV_SWIFTCALL)) { MonoClass *swift_self = mono_class_try_get_swift_self_class (); + MonoClass *swift_self_t = mono_class_try_get_swift_self_t_class (); MonoClass *swift_error = mono_class_try_get_swift_error_class (); MonoClass *swift_indirect_result = mono_class_try_get_swift_indirect_result_class (); MonoClass *swift_error_ptr = mono_class_create_ptr (m_class_get_this_arg (swift_error)); int swift_error_args = 0, swift_self_args = 0, swift_indirect_result_args = 0; for (int i = 0; i < method->signature->param_count; ++i) { MonoClass *param_klass = mono_class_from_mono_type_internal (method->signature->params [i]); + MonoGenericClass *param_gklass = mono_class_try_get_generic_class (param_klass); if (param_klass) { if (param_klass == swift_error && !m_type_is_byref (method->signature->params [i])) { swift_error_args = swift_self_args = 0; @@ -3711,7 +3714,15 @@ mono_marshal_get_native_wrapper (MonoMethod *method, gboolean check_exceptions, break; } else if (param_klass == swift_error || param_klass == swift_error_ptr) { swift_error_args++; - } else if (param_klass == swift_self) { + } else if (param_gklass && (param_gklass->container_class == swift_self_t) && i > 0) { + swift_error_args = swift_self_args = 0; + mono_error_set_generic_error (emitted_error, "System", "InvalidProgramException", "SwiftSelf must be the first argument in the signature."); + break; + } else if (param_gklass && (param_gklass->container_class == swift_self_t) && m_type_is_byref (method->signature->params [i])) { + swift_error_args = swift_self_args = 0; + mono_error_set_generic_error (emitted_error, "System", "InvalidProgramException", "Expected SwiftSelf struct, got pointer/reference."); + break; + } else if (param_klass == swift_self || (param_gklass && (param_gklass->container_class == swift_self_t))) { swift_self_args++; } else if (param_klass == swift_indirect_result) { swift_indirect_result_args++; diff --git a/src/mono/mono/metadata/marshal.h b/src/mono/mono/metadata/marshal.h index a682f4dc2b5101..d36ad269c78c72 100644 --- a/src/mono/mono/metadata/marshal.h +++ b/src/mono/mono/metadata/marshal.h @@ -753,6 +753,7 @@ IlgenCallbacksToMono* mono_marshal_get_mono_callbacks_for_ilgen (void); GENERATE_TRY_GET_CLASS_WITH_CACHE_DECL (swift_self) +GENERATE_TRY_GET_CLASS_WITH_CACHE_DECL (swift_self_t); GENERATE_TRY_GET_CLASS_WITH_CACHE_DECL (swift_error) GENERATE_TRY_GET_CLASS_WITH_CACHE_DECL (swift_indirect_result) diff --git a/src/mono/mono/mini/interp/transform.c b/src/mono/mono/mini/interp/transform.c index 3a6a2d3944903f..2b5e4c2c3fe619 100644 --- a/src/mono/mono/mini/interp/transform.c +++ b/src/mono/mono/mini/interp/transform.c @@ -3356,6 +3356,7 @@ interp_emit_swiftcall_struct_lowering (TransformData *td, MonoMethodSignature *c uint32_t new_param_count = 0; int align; MonoClass *swift_self = mono_class_try_get_swift_self_class (); + MonoClass *swift_self_t = mono_class_try_get_swift_self_t_class (); MonoClass *swift_error = mono_class_try_get_swift_error_class (); MonoClass *swift_indirect_result = mono_class_try_get_swift_indirect_result_class (); /* @@ -3366,6 +3367,8 @@ interp_emit_swiftcall_struct_lowering (TransformData *td, MonoMethodSignature *c for (int idx_param = 0; idx_param < csignature->param_count; ++idx_param) { MonoType *ptype = csignature->params [idx_param]; MonoClass *klass = mono_class_from_mono_type_internal (ptype); + MonoGenericClass *gklass = mono_class_try_get_generic_class (klass); + // SwiftSelf, SwiftError, and SwiftIndirectResult are special cases where we need to preserve the class information for the codegen to handle them correctly. if (mono_type_is_struct (ptype) && !(klass == swift_self || klass == swift_error || klass == swift_indirect_result)) { SwiftPhysicalLowering lowered_swift_struct = mono_marshal_get_swift_physical_lowering (ptype, FALSE); @@ -3386,8 +3389,13 @@ interp_emit_swiftcall_struct_lowering (TransformData *td, MonoMethodSignature *c g_array_append_val (new_params, lowered_swift_struct.lowered_elements [idx_lowered]); } } else { - // For structs that cannot be lowered, we change the argument to byref type - ptype = mono_class_get_byref_type (mono_defaults.typed_reference_class); + // For structs that cannot be lowered, we change the argument to a pointer-like argument type. + // If SwiftSelf can't be lowered, it should be passed in the same manner as SwiftSelf, via the context register. + if (gklass && (gklass->container_class == swift_self_t)) + ptype = mono_class_get_byref_type (swift_self); + else + ptype = mono_class_get_byref_type (klass); + // Load the address of the struct interp_add_ins (td, MINT_LDLOCA_S); interp_ins_set_sreg (td->last_ins, sp_old_params [idx_param].var); diff --git a/src/mono/mono/mini/method-to-ir.c b/src/mono/mono/mini/method-to-ir.c index d3b0637119d072..6734b2d0ce5c38 100644 --- a/src/mono/mono/mini/method-to-ir.c +++ b/src/mono/mono/mini/method-to-ir.c @@ -7533,6 +7533,7 @@ mono_method_to_ir (MonoCompile *cfg, MonoMethod *method, MonoBasicBlock *start_b GArray *new_params = g_array_sized_new (FALSE, FALSE, sizeof (MonoType*), n); uint32_t new_param_count = 0; MonoClass *swift_self = mono_class_try_get_swift_self_class (); + MonoClass *swift_self_t = mono_class_try_get_swift_self_t_class (); MonoClass *swift_error = mono_class_try_get_swift_error_class (); MonoClass *swift_indirect_result = mono_class_try_get_swift_indirect_result_class (); /* @@ -7543,6 +7544,7 @@ mono_method_to_ir (MonoCompile *cfg, MonoMethod *method, MonoBasicBlock *start_b for (int idx_param = 0; idx_param < n; ++idx_param) { MonoType *ptype = fsig->params [idx_param]; MonoClass *klass = mono_class_from_mono_type_internal (ptype); + MonoGenericClass *gklass = mono_class_try_get_generic_class (klass); // SwiftSelf, SwiftError, and SwiftIndirectResult are special cases where we need to preserve the class information for the codegen to handle them correctly. if (mono_type_is_struct (ptype) && !(klass == swift_self || klass == swift_error || klass == swift_indirect_result)) { @@ -7562,9 +7564,17 @@ mono_method_to_ir (MonoCompile *cfg, MonoMethod *method, MonoBasicBlock *start_b ++new_param_count; } } else { - // For structs that cannot be lowered, we change the argument to byref type + // For structs that cannot be lowered, we change the argument to a pointer-like argument type. + // If SwiftSelf can't be lowered, it should be passed in the same manner as SwiftSelf, via the context register. + if (gklass && (gklass->container_class == swift_self_t)) { + ptype = mono_class_get_byref_type (swift_self); + // The ARGLOADA should be a pointer-like type. + struct_base_address->klass = mono_defaults.int_class; + } else { + ptype = mono_class_get_byref_type (klass); + } + *sp++ = struct_base_address; - ptype = mono_class_get_byref_type (klass); g_array_append_val (new_params, ptype); ++new_param_count; diff --git a/src/mono/mono/mini/mini-amd64.c b/src/mono/mono/mini/mini-amd64.c index ed40ecadae0a6f..096fcea3e01d90 100644 --- a/src/mono/mono/mini/mini-amd64.c +++ b/src/mono/mono/mini/mini-amd64.c @@ -1096,8 +1096,7 @@ get_call_info (MonoMemPool *mp, MonoMethodSignature *sig) if ((klass == swift_self || klass == swift_indirect_result) && sig->pinvoke) { guint32 size = mini_type_stack_size_full (m_class_get_byval_arg (klass), NULL, sig->pinvoke && !sig->marshalling_disabled); - g_assert (size == 8); - + g_assert (size == TARGET_SIZEOF_VOID_P); ainfo->storage = ArgValuetypeInReg; ainfo->pair_storage [0] = ArgInIReg; ainfo->pair_storage [1] = ArgNone; diff --git a/src/mono/mono/mini/mini-arm64.c b/src/mono/mono/mini/mini-arm64.c index 903a8bc9965f73..d7f5c395829f5b 100644 --- a/src/mono/mono/mini/mini-arm64.c +++ b/src/mono/mono/mini/mini-arm64.c @@ -1922,8 +1922,7 @@ get_call_info (MonoMemPool *mp, MonoMethodSignature *sig) guint32 align; MonoType *ptype = mini_get_underlying_type (sig->params [pindex]); int size = mini_type_stack_size_full (ptype, &align, cinfo->pinvoke); - g_assert (size == 8); - + g_assert (size == TARGET_SIZEOF_VOID_P); ainfo->storage = ArgVtypeInIRegs; ainfo->reg = (klass == swift_self) ? ARMREG_R20 : ARMREG_R8; ainfo->nregs = 1; diff --git a/src/native/libs/System.Security.Cryptography.Native.Android/pal_jni.c b/src/native/libs/System.Security.Cryptography.Native.Android/pal_jni.c index ee31a3ac58d64e..ecb0c1f8ec43f9 100644 --- a/src/native/libs/System.Security.Cryptography.Native.Android/pal_jni.c +++ b/src/native/libs/System.Security.Cryptography.Native.Android/pal_jni.c @@ -451,8 +451,6 @@ jmethodID g_ByteBufferGet; jmethodID g_ByteBufferLimit; jmethodID g_ByteBufferPosition; jmethodID g_ByteBufferPutBuffer; -jmethodID g_ByteBufferPutByteArray; -jmethodID g_ByteBufferPutByteArrayWithLength; jmethodID g_ByteBufferRemaining; // javax/net/ssl/SSLContext @@ -477,6 +475,7 @@ jclass g_SSLEngineResult; jmethodID g_SSLEngineResultGetStatus; jmethodID g_SSLEngineResultGetHandshakeStatus; bool g_SSLEngineResultStatusLegacyOrder; +jmethodID g_SSLEngineResultBytesConsumed; // javax/crypto/KeyAgreement jclass g_KeyAgreementClass; @@ -1073,8 +1072,6 @@ jint AndroidCryptoNative_InitLibraryOnLoad (JavaVM *vm, void *reserved) g_ByteBufferLimit = GetMethod(env, false, g_ByteBuffer, "limit", "()I"); g_ByteBufferPosition = GetMethod(env, false, g_ByteBuffer, "position", "()I"); g_ByteBufferPutBuffer = GetMethod(env, false, g_ByteBuffer, "put", "(Ljava/nio/ByteBuffer;)Ljava/nio/ByteBuffer;"); - g_ByteBufferPutByteArray = GetMethod(env, false, g_ByteBuffer, "put", "([B)Ljava/nio/ByteBuffer;"); - g_ByteBufferPutByteArrayWithLength = GetMethod(env, false, g_ByteBuffer, "put", "([BII)Ljava/nio/ByteBuffer;"); g_ByteBufferRemaining = GetMethod(env, false, g_ByteBuffer, "remaining", "()I"); g_SSLContext = GetClassGRef(env, "javax/net/ssl/SSLContext"); @@ -1095,6 +1092,7 @@ jint AndroidCryptoNative_InitLibraryOnLoad (JavaVM *vm, void *reserved) g_SSLEngineResult = GetClassGRef(env, "javax/net/ssl/SSLEngineResult"); g_SSLEngineResultGetStatus = GetMethod(env, false, g_SSLEngineResult, "getStatus", "()Ljavax/net/ssl/SSLEngineResult$Status;"); g_SSLEngineResultGetHandshakeStatus = GetMethod(env, false, g_SSLEngineResult, "getHandshakeStatus", "()Ljavax/net/ssl/SSLEngineResult$HandshakeStatus;"); + g_SSLEngineResultBytesConsumed = GetMethod(env, false, g_SSLEngineResult, "bytesConsumed", "()I"); g_SSLEngineResultStatusLegacyOrder = android_get_device_api_level() < 24; g_KeyAgreementClass = GetClassGRef(env, "javax/crypto/KeyAgreement"); diff --git a/src/native/libs/System.Security.Cryptography.Native.Android/pal_jni.h b/src/native/libs/System.Security.Cryptography.Native.Android/pal_jni.h index 5f7d7c002c4147..2828a68dc03c03 100644 --- a/src/native/libs/System.Security.Cryptography.Native.Android/pal_jni.h +++ b/src/native/libs/System.Security.Cryptography.Native.Android/pal_jni.h @@ -465,8 +465,6 @@ extern jmethodID g_ByteBufferGet; extern jmethodID g_ByteBufferLimit; extern jmethodID g_ByteBufferPosition; extern jmethodID g_ByteBufferPutBuffer; -extern jmethodID g_ByteBufferPutByteArray; -extern jmethodID g_ByteBufferPutByteArrayWithLength; extern jmethodID g_ByteBufferRemaining; // javax/net/ssl/SSLContext @@ -491,6 +489,7 @@ extern jclass g_SSLEngineResult; extern jmethodID g_SSLEngineResultGetStatus; extern jmethodID g_SSLEngineResultGetHandshakeStatus; extern bool g_SSLEngineResultStatusLegacyOrder; +extern jmethodID g_SSLEngineResultBytesConsumed; // javax/crypto/KeyAgreement extern jclass g_KeyAgreementClass; diff --git a/src/native/libs/System.Security.Cryptography.Native.Android/pal_sslstream.c b/src/native/libs/System.Security.Cryptography.Native.Android/pal_sslstream.c index 9aa7444e391bcc..c7933f73aaee29 100644 --- a/src/native/libs/System.Security.Cryptography.Native.Android/pal_sslstream.c +++ b/src/native/libs/System.Security.Cryptography.Native.Android/pal_sslstream.c @@ -44,7 +44,7 @@ struct ApplicationProtocolData_t ARGS_NON_NULL(1) static uint16_t* AllocateString(JNIEnv* env, jstring source); ARGS_NON_NULL_ALL static PAL_SSLStreamStatus DoHandshake(JNIEnv* env, SSLStream* sslStream); -ARGS_NON_NULL_ALL static PAL_SSLStreamStatus DoWrap(JNIEnv* env, SSLStream* sslStream, int* handshakeStatus); +ARGS_NON_NULL_ALL static PAL_SSLStreamStatus DoWrap(JNIEnv* env, SSLStream* sslStream, int* handshakeStatus, int* bytesConsumed); ARGS_NON_NULL_ALL static PAL_SSLStreamStatus DoUnwrap(JNIEnv* env, SSLStream* sslStream, int* handshakeStatus); ARGS_NON_NULL_ALL static int GetHandshakeStatus(JNIEnv* env, SSLStream* sslStream) @@ -112,7 +112,7 @@ ARGS_NON_NULL_ALL static PAL_SSLStreamStatus Close(JNIEnv* env, SSLStream* sslSt { // Call wrap to clear any remaining data before closing int unused; - PAL_SSLStreamStatus ret = DoWrap(env, sslStream, &unused); + PAL_SSLStreamStatus ret = DoWrap(env, sslStream, &unused, &unused); // sslEngine.closeOutbound(); (*env)->CallVoidMethod(env, sslStream->sslEngine, g_SSLEngineCloseOutbound); @@ -120,7 +120,7 @@ ARGS_NON_NULL_ALL static PAL_SSLStreamStatus Close(JNIEnv* env, SSLStream* sslSt return ret; // Flush any remaining data (e.g. sending close notification) - return DoWrap(env, sslStream, &unused); + return DoWrap(env, sslStream, &unused, &unused); } ARGS_NON_NULL_ALL static PAL_SSLStreamStatus Flush(JNIEnv* env, SSLStream* sslStream) @@ -172,10 +172,14 @@ ARGS_NON_NULL_ALL static jobject ExpandBuffer(JNIEnv* env, jobject oldBuffer, in ARGS_NON_NULL_ALL static jobject EnsureRemaining(JNIEnv* env, jobject oldBuffer, int32_t newRemaining) { + IGNORE_RETURN((*env)->CallObjectMethod(env, oldBuffer, g_ByteBufferCompact)); + int32_t oldPosition = (*env)->CallIntMethod(env, oldBuffer, g_ByteBufferPosition); int32_t oldRemaining = (*env)->CallIntMethod(env, oldBuffer, g_ByteBufferRemaining); if (oldRemaining < newRemaining) { - return ExpandBuffer(env, oldBuffer, oldRemaining + newRemaining); + // After compacting the oldBuffer, the oldPosition is equal to the number of bytes in the buffer at the moment + // we need to change the capacity to the oldPosition + newRemaining + return ExpandBuffer(env, oldBuffer, oldPosition + newRemaining); } else { @@ -204,22 +208,19 @@ static int MapLegacySSLEngineResultStatus(int legacyStatus) } } -ARGS_NON_NULL_ALL static PAL_SSLStreamStatus DoWrap(JNIEnv* env, SSLStream* sslStream, int* handshakeStatus) +ARGS_NON_NULL_ALL static PAL_SSLStreamStatus WrapAndProcessResult(JNIEnv* env, SSLStream* sslStream, int* handshakeStatus, int* bytesConsumed, bool* repeat) { - // appOutBuffer.flip(); // SSLEngineResult result = sslEngine.wrap(appOutBuffer, netOutBuffer); - IGNORE_RETURN((*env)->CallObjectMethod(env, sslStream->appOutBuffer, g_ByteBufferFlip)); jobject result = (*env)->CallObjectMethod( env, sslStream->sslEngine, g_SSLEngineWrap, sslStream->appOutBuffer, sslStream->netOutBuffer); if (CheckJNIExceptions(env)) return SSLStreamStatus_Error; - // appOutBuffer.compact(); - IGNORE_RETURN((*env)->CallObjectMethod(env, sslStream->appOutBuffer, g_ByteBufferCompact)); - // handshakeStatus = result.getHandshakeStatus(); + // bytesConsumed = result.bytesConsumed(); // SSLEngineResult.Status status = result.getStatus(); *handshakeStatus = GetEnumAsInt(env, (*env)->CallObjectMethod(env, result, g_SSLEngineResultGetHandshakeStatus)); + *bytesConsumed = (*env)->CallIntMethod(env, result, g_SSLEngineResultBytesConsumed); int status = GetEnumAsInt(env, (*env)->CallObjectMethod(env, result, g_SSLEngineResultGetStatus)); (*env)->DeleteLocalRef(env, result); @@ -242,11 +243,10 @@ ARGS_NON_NULL_ALL static PAL_SSLStreamStatus DoWrap(JNIEnv* env, SSLStream* sslS } case STATUS__BUFFER_OVERFLOW: { - // Expand buffer - // int newCapacity = sslSession.getPacketBufferSize() + netOutBuffer.remaining(); - int32_t newCapacity = (*env)->CallIntMethod(env, sslStream->sslSession, g_SSLSessionGetPacketBufferSize) + - (*env)->CallIntMethod(env, sslStream->netOutBuffer, g_ByteBufferRemaining); - sslStream->netOutBuffer = ExpandBuffer(env, sslStream->netOutBuffer, newCapacity); + // Expand buffer and repeat the wrap + int32_t packetBufferSize = (*env)->CallIntMethod(env, sslStream->sslSession, g_SSLSessionGetPacketBufferSize); + sslStream->netOutBuffer = ExpandBuffer(env, sslStream->netOutBuffer, packetBufferSize); + *repeat = true; return SSLStreamStatus_OK; } default: @@ -257,18 +257,44 @@ ARGS_NON_NULL_ALL static PAL_SSLStreamStatus DoWrap(JNIEnv* env, SSLStream* sslS } } +ARGS_NON_NULL_ALL static PAL_SSLStreamStatus DoWrap(JNIEnv* env, SSLStream* sslStream, int* handshakeStatus, int* bytesConsumed) +{ + // appOutBuffer.flip(); + IGNORE_RETURN((*env)->CallObjectMethod(env, sslStream->appOutBuffer, g_ByteBufferFlip)); + + bool repeat = false; + PAL_SSLStreamStatus status = WrapAndProcessResult(env, sslStream, handshakeStatus, bytesConsumed, &repeat); + + if (repeat) + { + repeat = false; + status = WrapAndProcessResult(env, sslStream, handshakeStatus, bytesConsumed, &repeat); + + if (repeat) + { + LOG_ERROR("Unexpected repeat in DoWrap"); + return SSLStreamStatus_Error; + } + } + + // appOutBuffer.compact(); + IGNORE_RETURN((*env)->CallObjectMethod(env, sslStream->appOutBuffer, g_ByteBufferCompact)); + + return status; +} + ARGS_NON_NULL_ALL static PAL_SSLStreamStatus DoUnwrap(JNIEnv* env, SSLStream* sslStream, int* handshakeStatus) { // if (netInBuffer.position() == 0) // { - // byte[] tmp = new byte[netInBuffer.limit()]; - // int count = streamReader(tmp, 0, tmp.length); - // netInBuffer.put(tmp, 0, count); + // int netInBufferLimit = netInBuffer.limit(); + // ByteBuffer tmp = ByteBuffer.allocateDirect(netInBufferLimit); + // int count = streamReader(tmp, 0, netInBufferLimit); + // netInBuffer.put(tmp); // } if ((*env)->CallIntMethod(env, sslStream->netInBuffer, g_ByteBufferPosition) == 0) { int netInBufferLimit = (*env)->CallIntMethod(env, sslStream->netInBuffer, g_ByteBufferLimit); - jbyteArray tmp = make_java_byte_array(env, netInBufferLimit); uint8_t* tmpNative = (uint8_t*)xmalloc((size_t)netInBufferLimit); int count = netInBufferLimit; // todo assert streamReader != 0 ? @@ -276,13 +302,15 @@ ARGS_NON_NULL_ALL static PAL_SSLStreamStatus DoUnwrap(JNIEnv* env, SSLStream* ss if (status != SSLStreamStatus_OK) { free(tmpNative); - (*env)->DeleteLocalRef(env, tmp); return status; } - (*env)->SetByteArrayRegion(env, tmp, 0, count, (jbyte*)(tmpNative)); + jobject tmp = (*env)->NewDirectByteBuffer(env, tmpNative, count); + ON_EXCEPTION_PRINT_AND_GOTO(cleanup); + IGNORE_RETURN( - (*env)->CallObjectMethod(env, sslStream->netInBuffer, g_ByteBufferPutByteArrayWithLength, tmp, 0, count)); + (*env)->CallObjectMethod(env, sslStream->netInBuffer, g_ByteBufferPutBuffer, tmp)); +cleanup: free(tmpNative); (*env)->DeleteLocalRef(env, tmp); } @@ -350,13 +378,14 @@ ARGS_NON_NULL_ALL static PAL_SSLStreamStatus DoHandshake(JNIEnv* env, SSLStream* PAL_SSLStreamStatus status = SSLStreamStatus_OK; int handshakeStatus = GetHandshakeStatus(env, sslStream); assert(handshakeStatus >= 0); + int bytesConsumed; while (IsHandshaking(handshakeStatus) && status == SSLStreamStatus_OK) { switch (handshakeStatus) { case HANDSHAKE_STATUS__NEED_WRAP: - status = DoWrap(env, sslStream, &handshakeStatus); + status = DoWrap(env, sslStream, &handshakeStatus, &bytesConsumed); break; case HANDSHAKE_STATUS__NEED_UNWRAP: status = DoUnwrap(env, sslStream, &handshakeStatus); @@ -858,26 +887,24 @@ PAL_SSLStreamStatus AndroidCryptoNative_SSLStreamWrite(SSLStream* sslStream, uin JNIEnv* env = GetJNIEnv(); PAL_SSLStreamStatus ret = SSLStreamStatus_Error; - // int remaining = appOutBuffer.remaining(); - // int arraySize = length > remaining ? remaining : length; - // byte[] data = new byte[arraySize]; - int32_t remaining = (*env)->CallIntMethod(env, sslStream->appOutBuffer, g_ByteBufferRemaining); - int32_t arraySize = length > remaining ? remaining : length; - jbyteArray data = make_java_byte_array(env, arraySize); + // ByteBuffer bufferByteBuffer = ...; + jobject bufferByteBuffer = (*env)->NewDirectByteBuffer(env, buffer, length); + ON_EXCEPTION_PRINT_AND_GOTO(cleanup); + + // appOutBuffer.compact(); + // appOutBuffer = EnsureRemaining(appOutBuffer, length); + // appOutBuffer.put(bufferByteBuffer); + IGNORE_RETURN((*env)->CallObjectMethod(env, sslStream->appOutBuffer, g_ByteBufferCompact)); + sslStream->appOutBuffer = EnsureRemaining(env, sslStream->appOutBuffer, length); + IGNORE_RETURN((*env)->CallObjectMethod(env, sslStream->appOutBuffer, g_ByteBufferPutBuffer, bufferByteBuffer)); + ON_EXCEPTION_PRINT_AND_GOTO(cleanup); int32_t written = 0; while (written < length) { - int32_t toWrite = length - written > arraySize ? arraySize : length - written; - (*env)->SetByteArrayRegion(env, data, 0, toWrite, (jbyte*)(buffer + written)); - - // appOutBuffer.put(data, 0, toWrite); - IGNORE_RETURN((*env)->CallObjectMethod(env, sslStream->appOutBuffer, g_ByteBufferPutByteArrayWithLength, data, 0, toWrite)); - ON_EXCEPTION_PRINT_AND_GOTO(cleanup); - written += toWrite; - int handshakeStatus; - ret = DoWrap(env, sslStream, &handshakeStatus); + int bytesConsumed; + ret = DoWrap(env, sslStream, &handshakeStatus, &bytesConsumed); if (ret != SSLStreamStatus_OK) { goto cleanup; @@ -887,10 +914,12 @@ PAL_SSLStreamStatus AndroidCryptoNative_SSLStreamWrite(SSLStream* sslStream, uin ret = SSLStreamStatus_Renegotiate; goto cleanup; } + + written += bytesConsumed; } cleanup: - (*env)->DeleteLocalRef(env, data); + (*env)->DeleteLocalRef(env, bufferByteBuffer); return ret; } diff --git a/src/native/managed/cdacreader/src/Constants.cs b/src/native/managed/cdacreader/src/Constants.cs index 6ca10e46a949bb..d713a5e69598e3 100644 --- a/src/native/managed/cdacreader/src/Constants.cs +++ b/src/native/managed/cdacreader/src/Constants.cs @@ -14,9 +14,11 @@ internal static class Globals internal const string GCThread = nameof(GCThread); internal const string FeatureEHFunclets = nameof(FeatureEHFunclets); + internal const string ObjectToMethodTableUnmask = nameof(ObjectToMethodTableUnmask); internal const string SOSBreakingChangeVersion = nameof(SOSBreakingChangeVersion); internal const string FreeObjectMethodTable = nameof(FreeObjectMethodTable); + internal const string StringMethodTable = nameof(StringMethodTable); internal const string MiniMetaDataBuffAddress = nameof(MiniMetaDataBuffAddress); internal const string MiniMetaDataBuffMaxSize = nameof(MiniMetaDataBuffMaxSize); diff --git a/src/native/managed/cdacreader/src/Contracts/Object.cs b/src/native/managed/cdacreader/src/Contracts/Object.cs new file mode 100644 index 00000000000000..24dc8a8062fcd3 --- /dev/null +++ b/src/native/managed/cdacreader/src/Contracts/Object.cs @@ -0,0 +1,31 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; + +namespace Microsoft.Diagnostics.DataContractReader.Contracts; + +internal interface IObject : IContract +{ + static string IContract.Name { get; } = nameof(Object); + static IContract IContract.Create(Target target, int version) + { + ulong methodTableOffset = (ulong)target.GetTypeInfo(DataType.Object).Fields["m_pMethTab"].Offset; + byte objectToMethodTableUnmask = target.ReadGlobal(Constants.Globals.ObjectToMethodTableUnmask); + TargetPointer stringMethodTable = target.ReadPointer( + target.ReadGlobalPointer(Constants.Globals.StringMethodTable)); + return version switch + { + 1 => new Object_1(target, methodTableOffset, objectToMethodTableUnmask, stringMethodTable), + _ => default(Object), + }; + } + + public virtual TargetPointer GetMethodTableAddress(TargetPointer address) => throw new NotImplementedException(); + public virtual string GetStringValue(TargetPointer address) => throw new NotImplementedException(); +} + +internal readonly struct Object : IObject +{ + // Everything throws NotImplementedException +} diff --git a/src/native/managed/cdacreader/src/Contracts/Object_1.cs b/src/native/managed/cdacreader/src/Contracts/Object_1.cs new file mode 100644 index 00000000000000..1f30b15a7091cf --- /dev/null +++ b/src/native/managed/cdacreader/src/Contracts/Object_1.cs @@ -0,0 +1,41 @@ +// 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.Runtime.InteropServices; + +namespace Microsoft.Diagnostics.DataContractReader.Contracts; + +internal readonly struct Object_1 : IObject +{ + private readonly Target _target; + private readonly ulong _methodTableOffset; + private readonly TargetPointer _stringMethodTable; + private readonly byte _objectToMethodTableUnmask; + + internal Object_1(Target target, ulong methodTableOffset, byte objectToMethodTableUnmask, TargetPointer stringMethodTable) + { + _target = target; + _methodTableOffset = methodTableOffset; + _stringMethodTable = stringMethodTable; + _objectToMethodTableUnmask = objectToMethodTableUnmask; + } + + public TargetPointer GetMethodTableAddress(TargetPointer address) + { + TargetPointer mt = _target.ReadPointer(address + _methodTableOffset); + return mt.Value & (ulong)~_objectToMethodTableUnmask; + } + + string IObject.GetStringValue(TargetPointer address) + { + TargetPointer mt = GetMethodTableAddress(address); + if (mt != _stringMethodTable) + throw new ArgumentException("Address does not represent a string object", nameof(address)); + + Data.String str = _target.ProcessedData.GetOrAdd(address); + Span span = stackalloc byte[(int)str.StringLength * sizeof(char)]; + _target.ReadBuffer(str.FirstChar, span); + return new string(MemoryMarshal.Cast(span)); + } +} diff --git a/src/native/managed/cdacreader/src/Contracts/Registry.cs b/src/native/managed/cdacreader/src/Contracts/Registry.cs index 6a5bf35f64d648..0d6894e9fce27a 100644 --- a/src/native/managed/cdacreader/src/Contracts/Registry.cs +++ b/src/native/managed/cdacreader/src/Contracts/Registry.cs @@ -20,6 +20,7 @@ public Registry(Target target) public IException Exception => GetContract(); public ILoader Loader => GetContract(); + public IObject Object => GetContract(); public IThread Thread => GetContract(); public IRuntimeTypeSystem RuntimeTypeSystem => GetContract(); public IDacStreams DacStreams => GetContract(); diff --git a/src/native/managed/cdacreader/src/Data/String.cs b/src/native/managed/cdacreader/src/Data/String.cs new file mode 100644 index 00000000000000..088d1060a7d960 --- /dev/null +++ b/src/native/managed/cdacreader/src/Data/String.cs @@ -0,0 +1,21 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.Diagnostics.DataContractReader.Data; + +internal sealed class String : IData +{ + static String IData.Create(Target target, TargetPointer address) + => new String(target, address); + + public String(Target target, TargetPointer address) + { + Target.TypeInfo type = target.GetTypeInfo(DataType.String); + + FirstChar = address + (ulong)type.Fields["m_FirstChar"].Offset; + StringLength = target.Read(address + (ulong)type.Fields["m_StringLength"].Offset); + } + + public TargetPointer FirstChar { get; init; } + public uint StringLength { get; init; } +} diff --git a/src/native/managed/cdacreader/src/DataType.cs b/src/native/managed/cdacreader/src/DataType.cs index ef037be8ed1c8c..244d8ce4af12c4 100644 --- a/src/native/managed/cdacreader/src/DataType.cs +++ b/src/native/managed/cdacreader/src/DataType.cs @@ -37,4 +37,6 @@ public enum DataType TypeVarTypeDesc, FnPtrTypeDesc, DynamicMetadata, + Object, + String, } diff --git a/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs b/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs index e4f2968f29f797..9faba1459ad37c 100644 --- a/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs @@ -308,7 +308,21 @@ public unsafe int GetObjectExceptionData(ulong objectAddress, DacpExceptionObjec return HResults.S_OK; } - public unsafe int GetObjectStringData(ulong obj, uint count, char* stringData, uint* pNeeded) => HResults.E_NOTIMPL; + public unsafe int GetObjectStringData(ulong obj, uint count, char* stringData, uint* pNeeded) + { + try + { + Contracts.IObject contract = _target.Contracts.Object; + string str = contract.GetStringValue(obj); + CopyStringToTargetBuffer(stringData, count, pNeeded, str); + } + catch (System.Exception ex) + { + return ex.HResult; + } + + return HResults.S_OK; + } public unsafe int GetOOMData(ulong oomAddr, void* data) => HResults.E_NOTIMPL; public unsafe int GetOOMStaticData(void* data) => HResults.E_NOTIMPL; public unsafe int GetPEFileBase(ulong addr, ulong* peBase) => HResults.E_NOTIMPL; diff --git a/src/native/managed/cdacreader/tests/ObjectTests.cs b/src/native/managed/cdacreader/tests/ObjectTests.cs new file mode 100644 index 00000000000000..b95b7cac82c8ff --- /dev/null +++ b/src/native/managed/cdacreader/tests/ObjectTests.cs @@ -0,0 +1,169 @@ +// 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.Runtime.InteropServices; +using System.Text; +using Xunit; + +namespace Microsoft.Diagnostics.DataContractReader.UnitTests; + +public unsafe class ObjectTests +{ + const ulong TestStringMethodTableGlobalAddress = 0x00000000_100000a0; + const ulong TestStringMethodTableAddress = 0x00000000_100000a8; + + private static readonly Target.TypeInfo ObjectTypeInfo = new() + { + Fields = { + { "m_pMethTab", new() { Offset = 0, Type = DataType.pointer} }, + } + }; + + private static readonly Target.TypeInfo StringTypeInfo = new Target.TypeInfo() + { + Fields = { + { "m_StringLength", new() { Offset = 0x8, Type = DataType.uint32} }, + { "m_FirstChar", new() { Offset = 0xc, Type = DataType.uint16} }, + } + }; + + private static readonly (DataType Type, Target.TypeInfo Info)[] ObjectTypes = + [ + (DataType.Object, ObjectTypeInfo), + (DataType.String, StringTypeInfo), + ]; + + const ulong TestObjectToMethodTableUnmask = 0x7; + private static (string Name, ulong Value, string? Type)[] ObjectGlobals = + [ + (nameof(Constants.Globals.ObjectToMethodTableUnmask), TestObjectToMethodTableUnmask, "uint8"), + (nameof(Constants.Globals.StringMethodTable), TestStringMethodTableGlobalAddress, null), + ]; + + private static MockMemorySpace.Builder AddStringMethodTablePointer(TargetTestHelpers targetTestHelpers, MockMemorySpace.Builder builder) + { + MockMemorySpace.HeapFragment fragment = new() { Name = "Address of String Method Table", Address = TestStringMethodTableGlobalAddress, Data = new byte[targetTestHelpers.PointerSize] }; + targetTestHelpers.WritePointer(fragment.Data, TestStringMethodTableAddress); + return builder.AddHeapFragments([ + fragment, + new () { Name = "String Method Table", Address = TestStringMethodTableAddress, Data = new byte[targetTestHelpers.PointerSize] } + ]); + } + + private delegate MockMemorySpace.Builder ConfigureContextBuilder(MockMemorySpace.Builder builder); + + private static void ObjectContractHelper(MockTarget.Architecture arch, ConfigureContextBuilder configure, Action testCase) + { + TargetTestHelpers targetTestHelpers = new(arch); + string typesJson = TargetTestHelpers.MakeTypesJson(ObjectTypes); + string globalsJson = TargetTestHelpers.MakeGlobalsJson(ObjectGlobals); + byte[] json = Encoding.UTF8.GetBytes($$""" + { + "version": 0, + "baseline": "empty", + "contracts": { + "{{nameof(Contracts.Object)}}": 1 + }, + "types": { {{typesJson}} }, + "globals": { {{globalsJson}} } + } + """); + Span descriptor = stackalloc byte[targetTestHelpers.ContractDescriptorSize]; + targetTestHelpers.ContractDescriptorFill(descriptor, json.Length, ObjectGlobals.Length); + + int pointerSize = targetTestHelpers.PointerSize; + Span pointerData = stackalloc byte[ObjectGlobals.Length * pointerSize]; + for (int i = 0; i < ObjectGlobals.Length; i++) + { + var (_, value, _) = ObjectGlobals[i]; + targetTestHelpers.WritePointer(pointerData.Slice(i * pointerSize), value); + } + + fixed (byte* jsonPtr = json) + { + MockMemorySpace.Builder builder = new(); + builder = builder.SetDescriptor(descriptor) + .SetJson(json) + .SetPointerData(pointerData); + + builder = AddStringMethodTablePointer(targetTestHelpers, builder); + + if (configure != null) + { + builder = configure(builder); + } + + using MockMemorySpace.ReadContext context = builder.Create(); + + bool success = MockMemorySpace.TryCreateTarget(&context, out Target? target); + Assert.True(success); + testCase(target); + } + + GC.KeepAlive(json); + } + + private static MockMemorySpace.Builder AddObject(TargetTestHelpers targetTestHelpers, MockMemorySpace.Builder builder, TargetPointer address, TargetPointer methodTable) + { + MockMemorySpace.HeapFragment fragment = new() { Name = $"Object : MT = '{methodTable}'", Address = address, Data = new byte[targetTestHelpers.SizeOfTypeInfo(ObjectTypeInfo)] }; + Span dest = fragment.Data; + targetTestHelpers.WritePointer(dest.Slice(ObjectTypeInfo.Fields["m_pMethTab"].Offset), methodTable); + return builder.AddHeapFragment(fragment); + } + + private static MockMemorySpace.Builder AddStringObject(TargetTestHelpers targetTestHelpers, MockMemorySpace.Builder builder, TargetPointer address, string value) + { + int size = targetTestHelpers.SizeOfTypeInfo(ObjectTypeInfo) + targetTestHelpers.SizeOfTypeInfo(StringTypeInfo) + value.Length * sizeof(char); + MockMemorySpace.HeapFragment fragment = new() { Name = $"String = '{value}'", Address = address, Data = new byte[size] }; + Span dest = fragment.Data; + targetTestHelpers.WritePointer(dest.Slice(ObjectTypeInfo.Fields["m_pMethTab"].Offset), TestStringMethodTableAddress); + targetTestHelpers.Write(dest.Slice(StringTypeInfo.Fields["m_StringLength"].Offset), (uint)value.Length); + MemoryMarshal.Cast(value).CopyTo(dest.Slice(StringTypeInfo.Fields["m_FirstChar"].Offset)); + return builder.AddHeapFragment(fragment); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void UnmaskMethodTableAddress(MockTarget.Architecture arch) + { + const ulong TestObjectAddress = 0x00000000_10000010; + const ulong TestMethodTableAddress = 0x00000000_10000027; + TargetTestHelpers targetTestHelpers = new(arch); + ObjectContractHelper(arch, + (builder) => + { + builder = AddObject(targetTestHelpers, builder, TestObjectAddress, TestMethodTableAddress); + return builder; + }, + (target) => + { + Contracts.IObject contract = target.Contracts.Object; + Assert.NotNull(contract); + TargetPointer mt = contract.GetMethodTableAddress(TestObjectAddress); + Assert.Equal(TestMethodTableAddress & ~TestObjectToMethodTableUnmask, mt.Value); + }); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void StringValue(MockTarget.Architecture arch) + { + const ulong TestStringAddress = 0x00000000_10000010; + string expected = "test_string_value"; + TargetTestHelpers targetTestHelpers = new(arch); + ObjectContractHelper(arch, + (builder) => + { + builder = AddStringObject(targetTestHelpers, builder, TestStringAddress, expected); + return builder; + }, + (target) => + { + Contracts.IObject contract = target.Contracts.Object; + Assert.NotNull(contract); + string actual = contract.GetStringValue(TestStringAddress); + Assert.Equal(expected, actual); + }); + } +} diff --git a/src/tests/Interop/Swift/SwiftSelfContext/SwiftSelfContext.cs b/src/tests/Interop/Swift/SwiftSelfContext/SwiftSelfContext.cs index 77c046cf824805..8899985b99ac12 100644 --- a/src/tests/Interop/Swift/SwiftSelfContext/SwiftSelfContext.cs +++ b/src/tests/Interop/Swift/SwiftSelfContext/SwiftSelfContext.cs @@ -69,7 +69,6 @@ public struct FrozenNonEnregisteredStruct public static extern long SumFrozenNonEnregisteredStruct(SwiftSelf self); [Fact] - [SkipOnMono("SwiftSelf is not supported on Mono")] public unsafe static void TestSelfIsFrozenEnregisteredStruct() { long sum = SumFrozenEnregisteredStruct(new SwiftSelf(new FrozenEnregisteredStruct { A = 10, B = 20 })); @@ -77,7 +76,6 @@ public unsafe static void TestSelfIsFrozenEnregisteredStruct() } [Fact] - [SkipOnMono("SwiftSelf is not supported on Mono")] public unsafe static void TestSelfIsFrozenNonEnregisteredStruct() { long sum = SumFrozenNonEnregisteredStruct(new SwiftSelf(new FrozenNonEnregisteredStruct { A = 10, B = 20, C = 30, D = 40, E = 50 }));