-
Notifications
You must be signed in to change notification settings - Fork 5.3k
VM: Include generic instantiations in stack frames #96440
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
1e21c17
44f2275
250472a
dbc1445
a804a43
dc44a0d
869f439
fbe823e
2048e41
9ec2532
504c3fe
69ca3eb
fa307d5
93ee9a7
60d2c0c
a1598bb
f6e0743
e6cc527
a6d49b0
37502b1
c2742cc
359e396
07d86bb
f3118e1
70059c5
54790f7
8e9c556
6cc5a6a
bd83c8a
5465ff4
1a6c019
58ec764
657e2b3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7779,21 +7779,28 @@ static void getMethodInfoHelper( | |
|
|
||
| if (methInfo->options & CORINFO_GENERICS_CTXT_MASK) | ||
| { | ||
| #if defined(PROFILING_SUPPORTED) | ||
| BOOL fProfilerRequiresGenericsContextForEnterLeave = FALSE; | ||
| { | ||
| BEGIN_PROFILER_CALLBACK(CORProfilerPresent()); | ||
| if ((&g_profControlBlock)->RequiresGenericsContextForEnterLeave()) | ||
| { | ||
| fProfilerRequiresGenericsContextForEnterLeave = TRUE; | ||
| } | ||
| END_PROFILER_CALLBACK(); | ||
| } | ||
| if (fProfilerRequiresGenericsContextForEnterLeave) | ||
| if (ftn->IsJitOptimizationDisabled()) | ||
| { | ||
| methInfo->options = CorInfoOptions(methInfo->options|CORINFO_GENERICS_CTXT_KEEP_ALIVE); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @AndyAyersMS - Do you know if the JIT is currently preserving the generic context when optimizations are disabled somehow? I expected this would be something we already do but looking around at how CORINFO_GENERICS_CTXT_KEEP_ALIVE is used now that didn't seem to be the case.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is not. Generally speaking the generic context is only kept alive if we see the |
||
| } | ||
| else | ||
hez2010 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| { | ||
| #if defined(PROFILING_SUPPORTED) | ||
| BOOL fProfilerRequiresGenericsContextForEnterLeave = FALSE; | ||
| { | ||
| BEGIN_PROFILER_CALLBACK(CORProfilerPresent()); | ||
| if ((&g_profControlBlock)->RequiresGenericsContextForEnterLeave()) | ||
| { | ||
| fProfilerRequiresGenericsContextForEnterLeave = TRUE; | ||
| } | ||
| END_PROFILER_CALLBACK(); | ||
| } | ||
| if (fProfilerRequiresGenericsContextForEnterLeave) | ||
| { | ||
| methInfo->options = CorInfoOptions(methInfo->options|CORINFO_GENERICS_CTXT_KEEP_ALIVE); | ||
| } | ||
| #endif // defined(PROFILING_SUPPORTED) | ||
| } | ||
| } | ||
|
|
||
| PCCOR_SIGNATURE pSig = NULL; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -290,6 +290,45 @@ PTR_VOID CrawlFrame::GetExactGenericArgsToken() | |
| } | ||
| } | ||
|
|
||
| void CrawlFrame::InitializeExactGenericInstantiations() | ||
| { | ||
| CONTRACTL { | ||
| SUPPORTS_DAC; | ||
| NOTHROW; | ||
| GC_NOTRIGGER; | ||
| } CONTRACTL_END; | ||
|
|
||
| // Turn into cooperative GC mode | ||
| GCX_COOP(); | ||
|
|
||
| if (pFunc != NULL && pFunc->HasClassOrMethodInstantiation() && pFunc->IsSharedByGenericInstantiations()) | ||
| { | ||
| // Get exact instantiations for shared generics where possible | ||
| PTR_VOID pExactGenericArgsToken = GetExactGenericArgsToken(); | ||
|
|
||
| if (pExactGenericArgsToken != NULL) | ||
| { | ||
| TypeHandle th; | ||
| MethodDesc* pConstructedFunc = NULL; | ||
| if (Generics::GetExactInstantiationsOfMethodAndItsClassFromCallInformation(pFunc, pExactGenericArgsToken, &th, &pConstructedFunc)) | ||
| { | ||
| #ifndef DACCESS_COMPILE | ||
| if (pConstructedFunc->IsSharedByGenericInstantiations()) | ||
| { | ||
| InstantiatedMethodDesc *pInstMD = InstantiatedMethodDesc::FindLoadedInstantiatedMethodDesc(th.GetMethodTable(), pConstructedFunc->GetMemberDef(), Instantiation(), false); | ||
|
|
||
| if (pInstMD != NULL) | ||
| { | ||
| pConstructedFunc = pInstMD; | ||
| } | ||
|
Comment on lines
+318
to
+323
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It should really be pConstructedFunc = InstantiatedMethodDesc::FindOrCreateExactClassMethod(th.GetMethodTable(), pConstructedFunc);but this would possibly throw or trigger a GC. |
||
| } | ||
| #endif // !DACCESS_COMPILE | ||
| pFunc = pConstructedFunc; | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /* Is this frame at a safe spot for GC? | ||
| */ | ||
| bool CrawlFrame::IsGcSafe() | ||
|
|
@@ -3025,6 +3064,7 @@ void StackFrameIterator::ProcessCurrentFrame(void) | |
| #endif // FEATURE_EH_FUNCLETS | ||
|
|
||
| m_crawl.pFunc = m_crawl.codeInfo.GetMethodDesc(); | ||
| m_crawl.InitializeExactGenericInstantiations(); | ||
|
|
||
| // Cache values which may be updated by CheckForSkippedFrames() | ||
| m_cachedCodeInfo = m_crawl.codeInfo; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -116,8 +116,16 @@ public static IEnumerable<object[]> ToString_TestData() | |
| yield return new object[] { new StackFrame(), "MoveNext at offset {offset} in file:line:column {fileName}:{lineNumber}:{column}" + Environment.NewLine }; | ||
| yield return new object[] { new StackFrame("FileName", 1, 2), "MoveNext at offset {offset} in file:line:column FileName:1:2" + Environment.NewLine }; | ||
| yield return new object[] { new StackFrame(int.MaxValue), "<null>" + Environment.NewLine }; | ||
| yield return new object[] { GenericMethod<string>(), "GenericMethod<T> at offset {offset} in file:line:column {fileName}:{lineNumber}:{column}" + Environment.NewLine }; | ||
| yield return new object[] { GenericMethod<string, int>(), "GenericMethod<T,U> at offset {offset} in file:line:column {fileName}:{lineNumber}:{column}" + Environment.NewLine }; | ||
| if (AppContext.TryGetSwitch("Switch.System.Diagnostics.StackTrace.ShowGenericInstantiations", out var showGenericInstantiations) && showGenericInstantiations) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see this code that checks for the switch, but is there something which is setting the switch? I want to ensure the test is explicitly validating the behavior both with the switch enabled and disabled. |
||
| { | ||
| yield return new object[] { GenericMethod<string>(), "GenericMethod<System.String> at offset {offset} in file:line:column {fileName}:{lineNumber}:{column}" + Environment.NewLine }; | ||
| yield return new object[] { GenericMethod<string, int>(), "GenericMethod<System.String,System.Int32> at offset {offset} in file:line:column {fileName}:{lineNumber}:{column}" + Environment.NewLine }; | ||
| } | ||
| else | ||
| { | ||
| yield return new object[] { GenericMethod<string>(), "GenericMethod<T> at offset {offset} in file:line:column {fileName}:{lineNumber}:{column}" + Environment.NewLine }; | ||
| yield return new object[] { GenericMethod<string, int>(), "GenericMethod<T,U> at offset {offset} in file:line:column {fileName}:{lineNumber}:{column}" + Environment.NewLine }; | ||
| } | ||
| yield return new object[] { new ClassWithConstructor().StackFrame, ".ctor at offset {offset} in file:line:column {fileName}:{lineNumber}:{column}" + Environment.NewLine }; | ||
| } | ||
|
|
||
|
|
@@ -133,17 +141,17 @@ public void ToString_Invoke_ReturnsExpected(StackFrame stackFrame, string expect | |
| Assert.Equal(expectedToString, stackFrame.ToString()); | ||
| } | ||
|
|
||
| [MethodImpl(MethodImplOptions.NoInlining)] | ||
| [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] | ||
| private static StackFrame GenericMethod<T>() => new StackFrame(); | ||
|
|
||
| [MethodImpl(MethodImplOptions.NoInlining)] | ||
| [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] | ||
| private static StackFrame GenericMethod<T, U>() => new StackFrame(); | ||
|
|
||
| private class ClassWithConstructor | ||
| { | ||
| public StackFrame StackFrame { get; } | ||
|
|
||
| [MethodImpl(MethodImplOptions.NoInlining)] | ||
| [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] | ||
| public ClassWithConstructor() => StackFrame = new StackFrame(); | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -23,7 +23,13 @@ public void StackTraceTest() | |
| { | ||
| "System.Tests.EnvironmentStackTrace.StaticFrame(Object obj)", | ||
| "System.Tests.EnvironmentStackTrace.TestClass..ctor()", | ||
| "System.Tests.EnvironmentStackTrace.GenericFrame[T1,T2](T1 t1, T2 t2)", | ||
| AppContext.TryGetSwitch("Switch.System.Diagnostics.StackTrace.ShowGenericInstantiations", out var showGenericInstantiations) | ||
| ? | ||
| showGenericInstantiations | ||
| ? "System.Tests.EnvironmentStackTrace.GenericFrame[System.DateTime,System.Text.StringBuilder](DateTime t1, StringBuilder t2)" | ||
| : "System.Tests.EnvironmentStackTrace.GenericFrame[T1,T2](T1 t1, T2 t2)" | ||
| : "System.Tests.EnvironmentStackTrace.GenericFrame[T1,T2](T1 t1, T2 t2)" | ||
| , | ||
| "System.Tests.EnvironmentStackTrace.TestFrame()" | ||
| }; | ||
|
|
||
|
|
@@ -40,27 +46,27 @@ public void StackTraceTest() | |
| } | ||
| } | ||
|
|
||
| [MethodImpl(MethodImplOptions.NoInlining)] | ||
| [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't want to weaken the existing stackwalking test by only validating it against unoptimized code. You could create some alternate frames that are unoptimized and call those instead if the switch is set. (The same thing applies in the other tests)
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I also think we should explicitly have a test that demonstrates the behavior of a stack trace that occurs in optimized shared generic code where the shared generics context is no longer in scope. Even if it doesn't have exact type naming we still want to ensure it degrades gracefully rather than doing anything bad (like crashing). Was there ever an agreed upon behavior for what text gets shown for the generic parameters in an optimized shared generic frame? I believe the text "System.__Canon" has already leaked out in a few other places such as the error handling text @hez2010 mentioned or it has shown up in VS debugger callstacks for many years when debugging optimized generic code: I feel like the cat is already out of the bag in terms of the revealing that type name so using it here is aligned with precedent elsewhere. I'd also be fine reverting to use the type parameter name if you think its worth trying to get the cat back into the bag @jkotas?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We do not leak I agree with #96440 (comment) . |
||
| private void TestFrame() | ||
| { | ||
| GenericFrame<DateTime, StringBuilder>(DateTime.Now, null); | ||
| } | ||
|
|
||
| [MethodImpl(MethodImplOptions.NoInlining)] | ||
| [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] | ||
| private void GenericFrame<T1, T2>(T1 t1, T2 t2) | ||
| { | ||
| new TestClass(); | ||
| } | ||
|
|
||
| [MethodImpl(MethodImplOptions.NoInlining)] | ||
| [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] | ||
| private static void StaticFrame(object obj) | ||
| { | ||
| s_stackTrace = Environment.StackTrace; | ||
| } | ||
|
|
||
| private class TestClass | ||
| { | ||
| [MethodImpl(MethodImplOptions.NoInlining)] | ||
| [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] | ||
| public TestClass() | ||
| { | ||
| StaticFrame(null); | ||
|
|
||

Uh oh!
There was an error while loading. Please reload this page.