Skip to content
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

Closed
wants to merge 3 commits into from

Conversation

MichalStrehovsky
Copy link
Member

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

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.
@dotnet-issue-labeler dotnet-issue-labeler bot added the needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners label Jul 1, 2024
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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@am11 am11 added area-NativeAOT-coreclr and removed needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners labels Jul 1, 2024
Copy link
Contributor

Tagging subscribers to this area: @agocke, @MichalStrehovsky, @jkotas
See info in area-owners.md if you want to be subscribed.


// 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"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// 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);
Copy link
Member

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)?

Copy link
Member Author

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.

Copy link
Member

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.

Copy link
Member Author

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;
Copy link
Member

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)
Copy link
Contributor

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?

Copy link
Member Author

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?

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?

@MichalStrehovsky
Copy link
Member Author

On a second thought, it's better not to support this to put more pressure on dotnet/csharplang#4984 getting implemented.

@MichalStrehovsky MichalStrehovsky deleted the getcallingasm branch July 10, 2024 12:24
@github-actions github-actions bot locked and limited conversation to collaborators Aug 10, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Assembly.GetCallingAssembly() needs IL2026-like warning for Native AOT (and self contained) builds
5 participants