-
Notifications
You must be signed in to change notification settings - Fork 4.5k
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
Create a managed implementation of assembly binder #91400
base: main
Are you sure you want to change the base?
Conversation
Tagging subscribers to this area: @vitek-karas, @agocke, @VSadov Issue Details
I've ported the types under The logic is ported closely from C++ and aims line-to-line match, to keep the behavior. Initially I want to convert all the HRESULTs to exceptions, but it's not practical to use exception in control logic of binder, so I kept HRESULTs in AssemblyBinderCommon. /cc @jkotas Is this in the desired direction? I haven't do anything around managed/unmanaged boundary. I think code should be ported to managed, until something we don't want.
|
This is good raw material. Creating a clean managed/unmanaged boundary is probably going to be the harder part. Some thoughts:
|
src/coreclr/System.Private.CoreLib/src/Internal/Runtime/Binder/AssemblyName.cs
Outdated
Show resolved
Hide resolved
src/coreclr/System.Private.CoreLib/src/Internal/Runtime/Binder/ApplicationContext.cs
Outdated
Show resolved
Hide resolved
int* dwPAFlags = stackalloc int[2]; | ||
using IMdInternalImport pIMetaDataAssemblyImport = BinderAcquireImport(pPEImage, dwPAFlags); | ||
|
||
Architecture = AssemblyBinderCommon.TranslatePEToArchitectureType(dwPAFlags); |
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.
The managed binder should not need to deal with image architectures. The unmanaged runtime should validate very early whether the image is good for current architecture (and reject it if it is not). By the time we get to the managed binder, we should know that the image architecture is good.
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.
I started with focusing the data structures, and did not look at the caller. Leaving the cleanup later should be fine, since cleaning managed code is easy.
@elinor-fung FYI |
Good points for the behaviors that we don't need in managed binder. I'm aiming for a working POC first, and not tweaking yet. Records are just simplification since I don't want to write GetHashCode etc. first. Rough thought of steps:
Questions:
|
@jkotas More for my own education, rather than as a comment on the details of this PR: how does a managed assembly binder interact with class loading/initialization in coreclr? In Mono we have scenarios where constructing a class triggers assembly loading while we're holding the global loader lock - which can run managed loader events which can lead to deadlocks (#72796) . My understanding is that CoreCLR class loading is less atomic - relying on load levels and individual class locks. But I was under the impression that there are still scenarios where managed callbacks fire while a native lock is held. Does moving the assembly binder to managed code affect those scenarios? Also what happens if you trigger class initialization from the managed assembly binder? One way I could confuse mono was by doing a It's possible that the interaction between the assembly binder and class initialization is more well behaved in CoreCLR, but I'm curious if porting the binder to managed has any impact. |
Right, assembly loader needs to be able to call AssemblyLoadContext callbacks and AppDomain events that are managed code. Rewriting more of the assembly loader in managed code means that there is going to be more managed code running, but it does not change any fundamental invariants.
CoreCLR class loading/initialization has lock per type. (The implementation of this lock is being rewritten in #96653 to be more scalable and performant.) Triggering assembly loading or more type loading from type loading works fine, as long as there is no recursive loading of the same type.
CoreCLR does not trigger class initializers during type or assembly loader. The class initializers are triggered only once the code runs. We are out of the assembly and type loader by that time. |
Moved to draft while we figure out if we can either fix the perf regressions, or accept them. |
I've done some measurement about working set: It's about 3.5MB of total regression comparing to main for a given run. Break down by vmmap: So one obvious conclusion is that some globalization related code are touched unintentionally. Setting globalization invariant mode doesn't change the regression though. |
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.
There are multiple cases in the code that might trigger globalization code
src/coreclr/System.Private.CoreLib/src/System/Runtime/Loader/ApplicationContext.cs
Outdated
Show resolved
Hide resolved
src/coreclr/System.Private.CoreLib/src/System/Runtime/Loader/AssemblyName.cs
Outdated
Show resolved
Hide resolved
src/coreclr/System.Private.CoreLib/src/System/Runtime/Loader/AssemblyName.cs
Outdated
Show resolved
Hide resolved
src/coreclr/System.Private.CoreLib/src/System/Runtime/Loader/AssemblyName.cs
Outdated
Show resolved
Hide resolved
Draft Pull Request was automatically closed for 30 days of inactivity. Please let us know if you'd like to reopen it. |
Keep open since it's functionally complete. |
Draft Pull Request was automatically closed for 30 days of inactivity. Please let us know if you'd like to reopen it. |
Status report:Working Set:About 15.2 MB -> 16.3 MB under default configuration. Start time:Measured with File size:-39KB in coreclr.dll, +92KB in CoreLib (R2R). There are still some space of dead code for optimization, but I kept this PR as close to native code as possible to help reviewing. |
if (outPath.EndsWith(".ni.dll", StringComparison.OrdinalIgnoreCase) | ||
|| outPath.EndsWith(".ni.exe", StringComparison.OrdinalIgnoreCase)) | ||
{ | ||
simpleName = outPath[iSimpleNameStart..^7]; | ||
isNativeImage = true; | ||
} |
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 ni.dll
still a thing? I remember it's output of NGen.
Even if it's still a thing, we should probably remove this too because the shared framework (TPA) does not contain any ni files.
The performance difference is negligible though.
#85558 (comment)
I've ported the types under
BINDER_SPACE::
, and a large portion of binding logic in assemblybindercommon.cpp. I also created a disabled feature switch to guard the code.The logic is ported closely from C++ and aims line-to-line match, to keep the behavior. Initially I want to convert all the HRESULTs to exceptions, but it's not practical to use exception in control logic of binder, so I kept HRESULTs in AssemblyBinderCommon.
/cc @jkotas
Is this in the desired direction? I haven't do anything around managed/unmanaged boundary. I think code should be ported to managed, until something we don't want.