[Hot Reload] ManagedRegistrarStep: emit UnmanagedCallersOnly trampolines into a companion assembly. Fixes #26074.#26139
Conversation
… companion-assembly factory (#26074) Groundwork for relocating the trimmable-static registrar's [UnmanagedCallersOnly] trampolines out of user assemblies (so they stay byte-unmodified for Hot Reload) and into the per-assembly companion '_<Asm>.TypeMap.dll'. - Add the $(HotReloadCompatibleBuild) property plumbing: Application flag, LinkerConfiguration load/save, _CustomLinkerOptions wiring and docs. - Extract the companion-assembly creation from TrimmableRegistrarStep into a new shared RegistrarCompanionAssembly.GetOrCreate factory, stored in LinkerConfiguration.RegistrarCompanionAssemblies, so ManagedRegistrarStep can create/emit into the same companion earlier. TrimmableRegistrarStep now reuses it (behavior-preserving). Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…o companion assembly (#26074) When HotReloadCompatibleBuild is enabled with the TrimmableStatic registrar, emit the [UnmanagedCallersOnly] registrar trampolines (and constructor helpers) into the per-assembly companion assembly (_<Asm>.TypeMap.dll) instead of the user assembly, so user assemblies stay byte-unmodified (a Hot Reload requirement). Release builds (property disabled) keep the current in-user-assembly behavior. - ManagedRegistrarStep: when relocating, switch the AppBundleRewriter to the companion assembly, emit the trampoline into a top-level __Registrar_Callbacks__ type there, skip the user-side [DynamicDependency], re-import all references into the companion module, and track user-assembly modifications precisely so the user assembly isn't re-serialized. - Constructors: instead of cloning a constructor into the user type, emit an inline factory (RuntimeHelpers.GetUninitializedObject + set handle/flags + call the real constructor) in the companion trampoline, and grant the companion access to the platform assembly's NSObject handle/flags setters. - CollectUnmanagedCallersMethod: in the post-processing pass, resolve the relocated trampolines from the companion assembly instead of the user type. - AppBundleRewriter: add RuntimeHelpers.GetUninitializedObject references. Only non-generic types are relocated for now; generic-type proxy relocation is a follow-up. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…cation (#26074) Adds a structural unit test verifying that when HotReloadCompatibleBuild is enabled with the TrimmableStatic registrar, the registrar trampolines are emitted into the companion assembly (_Test.TypeMap.dll) and the user assembly is left byte-unmodified (not re-saved, no __Registrar_Callbacks__ type, no cloned ctor, no injected [DynamicDependency]). The test can't run in this environment (requires Xcode 26.6 to build the workload); CI verifies it. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR updates the trimmable static registrar pipeline to support Hot Reload compatibility by relocating [UnmanagedCallersOnly] registrar trampolines (and constructor helper logic) out of user assemblies and into the per-assembly companion _\<Asm>.TypeMap.dll assembly, so user assemblies can remain byte-for-byte unchanged when $(HotReloadCompatibleBuild) is enabled.
Changes:
- Introduces a shared
RegistrarCompanionAssembly.GetOrCreatefactory soManagedRegistrarStepandTrimmableRegistrarStepcan share the same companion assembly instance. - Relocates non-generic registrar trampolines (and ctor helper behavior) into the companion assembly, with reference re-importing to ensure valid Cecil module ownership.
- Adds an assembly-preparer test asserting the user assembly is not re-saved and that trampolines live in the companion assembly, plus MSBuild plumbing/docs for
HotReloadCompatibleBuild.
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| tools/dotnet-linker/Steps/TrimmableRegistrarStep.cs | Reuses/creates the per-assembly companion TypeMap assembly via RegistrarCompanionAssembly.GetOrCreate. |
| tools/dotnet-linker/Steps/ManagedRegistrarStep.cs | Relocates [UnmanagedCallersOnly] trampolines + ctor helper logic into the companion assembly when Hot Reload compatibility is enabled. |
| tools/dotnet-linker/RegistrarCompanionAssembly.cs | New shared helper to create/cache companion assemblies and track IgnoresAccessChecksTo needs. |
| tools/dotnet-linker/LinkerConfiguration.cs | Adds companion-assembly cache + config plumbing for HotReloadCompatibleBuild. |
| tools/dotnet-linker/AppBundleRewriter.cs | Adds Cecil helpers for RuntimeHelpers.GetUninitializedObject to support relocated ctor logic. |
| tools/common/Application.cs | Adds HotReloadCompatibleBuild flag to the tool configuration surface. |
| tools/assembly-preparer/assembly-preparer.csproj | Includes the new companion-assembly helper in assembly-preparer build. |
| tests/assembly-preparer/RelocateRegistrarTrampolinesTests.cs | New structural test verifying trampolines are relocated and user assembly remains untouched. |
| tests/assembly-preparer/BaseClass.cs | Adds a helper to run assembly-preparer in Hot Reload + trimmable-static mode and return user/companion assemblies. |
| dotnet/targets/Xamarin.Shared.Sdk.targets | Flows $(HotReloadCompatibleBuild) into _CustomLinkerOptions. |
| docs/building-apps/build-properties.md | Documents the new HotReloadCompatibleBuild MSBuild property. |
| var relocate = ShouldRelocateTrampolines (method); | ||
| RegistrarCompanionAssembly? companion = null; | ||
| if (relocate) { |
✅ API diff for current PR / commitNET (empty diffs)✅ API diff vs stableNET (empty diffs)ℹ️ Generator diffGenerator Diff: vsdrops (html) vsdrops (raw diff) gist (raw diff) - Please review changes) Pipeline on Agent |
🔥 [PR Build #93534bf] Build failed (Build macOS tests) 🔥Build failed for the job 'Build macOS tests' (with job status 'Failed') Pipeline on Agent |
🔥 [CI Build #93534bf] Test results 🔥Test results❌ Tests failed on VSTS: test results 7 tests crashed, 145 tests failed, 13 tests passed. Failures❌ assembly-processing tests1 tests failed, 0 tests passed.Failed tests
Html Report (VSDrops) Download ❌ dotnettests tests (iOS)1 tests failed, 0 tests passed.Failed tests
Html Report (VSDrops) Download ❌ dotnettests tests (MacCatalyst)1 tests failed, 0 tests passed.Failed tests
Html Report (VSDrops) Download ❌ dotnettests tests (macOS)1 tests failed, 0 tests passed.Failed tests
Html Report (VSDrops) Download ❌ dotnettests tests (tvOS)1 tests failed, 0 tests passed.Failed tests
Html Report (VSDrops) Download ❌ framework tests🔥 Failed catastrophically on VSTS: test results - framework (no summary found). Html Report (VSDrops) Download ❌ fsharp tests4 tests failed, 0 tests passed.Failed tests
Html Report (VSDrops) Download ❌ interdependent-binding-projects tests4 tests failed, 0 tests passed.Failed tests
Html Report (VSDrops) Download ❌ introspection tests4 tests failed, 0 tests passed.Failed tests
Html Report (VSDrops) Download ❌ linker tests (iOS)15 tests failed, 0 tests passed.Failed tests
Html Report (VSDrops) Download ❌ linker tests (MacCatalyst)15 tests failed, 0 tests passed.Failed tests
Html Report (VSDrops) Download ❌ linker tests (macOS)21 tests failed, 0 tests passed.Failed tests
Html Report (VSDrops) Download ❌ linker tests (tvOS)15 tests failed, 0 tests passed.Failed tests
Html Report (VSDrops) Download ❌ monotouch tests (iOS)19 tests failed, 0 tests passed.Failed tests
Html Report (VSDrops) Download ❌ monotouch tests (MacCatalyst)🔥 Failed catastrophically on VSTS: test results - monotouch_maccatalyst (no summary found). Html Report (VSDrops) Download ❌ monotouch tests (macOS)19 tests failed, 0 tests passed.Failed tests
Html Report (VSDrops) Download ❌ monotouch tests (tvOS)19 tests failed, 0 tests passed.Failed tests
Html Report (VSDrops) Download ❌ windows tests1 tests failed, 2 tests passed.Failed tests
Html Report (VSDrops) Download ❌ xcframework tests4 tests failed, 0 tests passed.Failed tests
Html Report (VSDrops) Download ❌ Tests on macOS Monterey (12) testsHtml Report (VSDrops) Download ❌ Tests on macOS Ventura (13) testsHtml Report (VSDrops) Download ❌ Tests on macOS Sonoma (14) testsHtml Report (VSDrops) Download ❌ Tests on macOS Sequoia (15) testsHtml Report (VSDrops) Download ❌ Tests on macOS Tahoe (26) testsHtml Report (VSDrops) Download Successes✅ cecil: All 1 tests passed. Html Report (VSDrops) Download |
…hot-reload-managedregistrarstep-emit-un-fd73d5
- ManagedRegistrarStep: set modifiedCurrentAssembly when a trampoline (or a generic-type proxy interface/implementation) is emitted into the user assembly on the non-relocated path. Previously the flag was only set on the ImplementConstruct paths, so with HotReloadCompatibleBuild enabled a modified user assembly (e.g. one with generic exported types) could be skipped when saving, dropping the generated trampolines/proxies. (review: error) - Reconcile with the HotReloadCompatibleBuild property that landed on main via a sibling PR: drop the duplicate Application.HotReloadCompatibleBuild field, the duplicate config-key handler (a duplicate key in the collection initializer would throw at runtime) and the duplicate _CustomLinkerOptions entry in Xamarin.Shared.Sdk.targets; use Configuration.HotReloadCompatibleBuild everywhere. (review: warning) - Make RegistrarCompanionAssembly internal + sealed (and the RegistrarCompanionAssemblies dictionary internal to match). (review: suggestion) Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Part of the Hot Reload epic (#26069). When
$(HotReloadCompatibleBuild)is enabled with the TrimmableStatic registrar, user assemblies must stay byte-for-byte unmodified. TodayManagedRegistrarStepemits the[UnmanagedCallersOnly]registrar trampolines (__Registrar_Callbacks__) and per-type constructor helpers directly into the user assembly, which breaks that requirement.This PR relocates those trampolines (and the constructor helpers) into the per-assembly companion assembly (
_<Asm>.TypeMap.dll) that the trimmable static registrar already produces, so the user assembly is left untouched. Release builds (property disabled) keep the current behavior.What changed
$(HotReloadCompatibleBuild)plumbing: property flows through_CustomLinkerOptionsintoApplication/LinkerConfiguration; documented indocs/building-apps/build-properties.md. (Minimal self-contained plumbing; the property is fully owned by [Hot Reload] Add $(HotReloadCompatibleBuild) MSBuild property + assembly-preparer safety net #26072.)RegistrarCompanionAssembly.GetOrCreate— the companion_<Asm>.TypeMap.dllis now created once and shared betweenManagedRegistrarStep(which emits the trampolines into it) andTrimmableRegistrarStep(which reuses it for the type map).ManagedRegistrarSteprelocation: when relocating, the trampolines are emitted into a top-level__Registrar_Callbacks__in the companion; the user-side[DynamicDependency]is skipped (the trampoline stays alive via the companion'sldftnreference); leftover user-module references are re-imported into the companion module. The injected cloned constructor is replaced by an inline factory in the trampoline (RuntimeHelpers.GetUninitializedObject+ set handle/flags +callthe real ctor), so no constructor is added to the user type. Save is now gated on precise modification tracking so a fully-relocated user assembly is never re-serialized.Scope
Non-generic trampolines (including constructors) are relocated now. Generic-type proxy relocation is deferred to a follow-up.
Testing
Adds a structural assembly-preparer test (
RelocateRegistrarTrampolinesTests) asserting the user assembly is not re-saved and has no__Registrar_Callbacks__/cloned ctor/callback[DynamicDependency], while the companion holds the[UnmanagedCallersOnly]trampolines. The full build/runtime path requires Xcode 26.6, which isn't available in the dev environment, so CI verifies the runtime behavior.Fixes #26074
🤖 Pull request created by Copilot