-
Notifications
You must be signed in to change notification settings - Fork 4.8k
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
Implement Assembly.GetCallingAssembly
for native AOT
#104229
Conversation
The problem with this API has always been the possible non-existence of reflection metadata about the calling method. But I realized that the reflection metadata can be supplemented by stack trace metadata that also knows assemblies of all methods on the stack. So this API is supportable as long as stack traces are not disabled. It's also conveniently easy to implement with the new `DiagnosticMethodInfo` API we added in .NET 9. I'm not sure if we want to go as far as make this API not work on CoreCLR with JIT if `StackTraceSupport` is false, but we could do that. It might be preferable, but a bit breaking. Resolves dotnet#94200.
public static Assembly GetCallingAssembly() | ||
{ | ||
if (AppContext.TryGetSwitch("Switch.System.Reflection.Assembly.SimulatedCallingAssembly", out bool isSimulated) && isSimulated) | ||
return GetEntryAssembly(); | ||
|
||
throw new PlatformNotSupportedException(); | ||
if (!StackTrace.IsSupported) | ||
throw new NotSupportedException(SR.NotSupported_StackTraceSupportDisabled); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can update the relevant section at the same time: https://github.com/dotnet/runtime/blob/f8bcb89796d3c83264fffd913e5196d29d8d730e/src/coreclr/nativeaot/docs/reflection-in-aot-mode.md#simulated-calling-assembly
Tagging subscribers to this area: @agocke, @MichalStrehovsky, @jkotas |
src/libraries/System.Private.CoreLib/src/Resources/Strings.resx
Outdated
Show resolved
Hide resolved
Co-authored-by: Martin Costello <[email protected]>
|
||
// We want to be able to handle GetCallingAssembly being called from Main (CoreCLR returns | ||
// the assembly of Main), and also GetCallingAssembly being called from things like | ||
// delegate invoke thunks. We do this by making the the definition of "calling assembly" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// delegate invoke thunks. We do this by making the the definition of "calling assembly" | |
// delegate invoke thunks. We do this by making the definition of "calling assembly" |
// Technically we want skipFrames: 2 since we're interested in the frame that | ||
// called the method that calls GetCallingAssembly, but we might need the first frame | ||
// later in this method. | ||
var stackTrace = new StackTrace(skipFrames: 1); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks expensive. What's the performance of this implementation of GetCallingAssembly
compared to regular CoreCLR in typical case (e.g. GetCallingAssembly
called somewhere from ASP.NET)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is your concern that we obtain entire stack trace as opposed to just a single frame? Both of these end up doing similar work anyway (they call RhGetCurrentStackTrace), just one of them will throw out everything but the one frame.
We could introduce a new specialized stack walking API, but I'm only doing this because it's cheap to implement.
I'm also fine just won't fixing the bug and telling people they can't do this until dotnet/csharplang#4984 is implemented because once this is no longer cheap to implement, it's not worth the effort.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My concern was that this is replacing naot functionality issue with naot-specific performance issue. The performance of this implementation can be much worse compared to CoreCLR one.
introducing specialized stackwalking method that does not walk the whole stack and some perf numbers would address my concern.
I am fine with won’t fixing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
introducing specialized stackwalking method that does not walk the whole stack and some perf numbers would address my concern.
Right, that would double the amount of work and risk/bug potential, tipping this over to the "not worth the effort given the limited benefit" category. Very few people missed this on .NET Native, and so do on native AOT. The csharplang issue is a much better fix. The only reason why I even looked at this is not that some users would benefit from this, but that we produce no warning about potential behavior difference, but this is a rarely used API and I can live with that hole.
// do if GetCallingAssembly is called from e.g. Main. | ||
dmi ??= stackTrace.GetFrame(0) is StackFrame sf ? DiagnosticMethodInfo.Create(sf) : null; | ||
|
||
return dmi.DeclaringAssemblyName is string asmName ? Load(asmName) : null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can dmi
be null here?
MethodDesc caller = HandleToObject(callerHnd); | ||
|
||
// Do not tailcall out of the entry point as it results in a confusing debugger experience. | ||
if (caller is EcmaMethod em && em.Module.EntryPoint == caller) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we return true
here and in the last check if StackTraceSupport
is disabled? Same question for that CORINFO_FLG_DONT_INLINE_CALLER
flag above that's not used on AOT too: could we not skip that too when StackTraceSupport
is disabled, so we never miss optimizations there if we don't care about the stack trace anyway?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we return
true
here and in the last check ifStackTraceSupport
is disabled? Same question for thatCORINFO_FLG_DONT_INLINE_CALLER
flag above that's not used on AOT too: could we not skip that too whenStackTraceSupport
is disabled, so we never miss optimizations there if we don't care about the stack trace anyway?
What would be the advantage? Conditioning this on StackTraceSupport would only make sense if we also check this is GetCallingAssembly (and not some other method marked DynamicMethod). But in that case this API is going to throw anyway. Do we need it to throw faster?
On a second thought, it's better not to support this to put more pressure on dotnet/csharplang#4984 getting implemented. |
The problem with this API has always been the possible non-existence of reflection metadata about the calling method. But I realized that the reflection metadata can be supplemented by stack trace metadata that also knows assemblies of all methods on the stack.
So this API is supportable as long as stack traces are not disabled. It's also conveniently easy to implement with the new
DiagnosticMethodInfo
API we added in .NET 9.I'm not sure if we want to go as far as make this API not work on CoreCLR with JIT if
StackTraceSupport
is false, but we could do that. It might be preferable, but a bit breaking.Resolves #94200.
Cc @dotnet/ilc-contrib