Skip to content

[TrimmableTypeMap] Add TypeMap proxy groundwork with CreateInstance - #10808

Merged
jonathanpeppers merged 44 commits into
mainfrom
dev/simonrozsival/trimmable-typemap-03-generators
Mar 11, 2026
Merged

[TrimmableTypeMap] Add TypeMap proxy groundwork with CreateInstance#10808
jonathanpeppers merged 44 commits into
mainfrom
dev/simonrozsival/trimmable-typemap-03-generators

Conversation

@simonrozsival

@simonrozsivalsimonrozsival commented Feb 12, 2026

Copy link
Copy Markdown
Member

Part of #10799

Summary

Adds the core pipeline that transforms scanned JavaPeerInfo records into TypeMap .dll assemblies with proxy types and CreateInstance support.

Generator pipeline

 Input assemblies (*.dll)
│
▼
┌───────────────┐
│ JavaPeerScanner│ Scans PE metadata for [Register] types,
└───────┬───────┘ activation ctors, interfaces, base types
│
│ List
▼
┌───────────────┐
│ ModelBuilder │ Alias detection, ACW filtering, proxy naming,
└───────┬───────┘ unconditional vs trimmable classification
│
│ TypeMapAssemblyData
▼
┌──────────────────────┐
│ TypeMapAssemblyEmitter│ IL emission: proxy types, CreateInstance,
│ + PEAssemblyBuilder │ TypeMap attributes, IgnoresAccessChecksTo
└──────────┬───────────┘
│
│ PE bytes
▼
┌───────────────┐
│ Output *.dll │ _MyApp.TypeMap.dll (per-assembly)
└───────────────┘ _Microsoft.Android.TypeMaps.dll (root)

Tests

  • 141 tests covering model builder, assembly generator, root assembly generator, and scanner behavior
  • Test fixtures include MCW bindings, user ACW types, interfaces with invokers, nested types, generics, Implementor/EventDispatcher helpers, JI-style activation ctors, and edge cases

Future optimization

UCO wrappers, RegisterNatives, and IAndroidCallableWrapper support will be added in follow-up PRs.

Follow-up work

@simonrozsival
simonrozsivalforce-pushed the dev/simonrozsival/trimmable-typemap-02-scanner branch 5 times, most recently from 627fef2 to 408d73dCompareFebruary 13, 2026 09:25
@simonrozsival
simonrozsivalforce-pushed the dev/simonrozsival/trimmable-typemap-03-generators branch 2 times, most recently from ba936c9 to 61ac7edCompareFebruary 13, 2026 10:35
@simonrozsival
simonrozsivalforce-pushed the dev/simonrozsival/trimmable-typemap-03-generators branch 2 times, most recently from ddddd2f to 83a50beCompareFebruary 13, 2026 11:22
@simonrozsivalsimonrozsival added Area: NativeAOT Issues that only occur when using NativeAOT. Area: CoreCLR Issues that only occur when using CoreCLR. copilot `copilot-cli` or other AIs were used to author this trimmable-type-map labels Feb 13, 2026
@simonrozsival
simonrozsivalforce-pushed the dev/simonrozsival/trimmable-typemap-03-generators branch from 83a50be to 4e7d06cCompareFebruary 13, 2026 17:12
@simonrozsival
simonrozsivalforce-pushed the dev/simonrozsival/trimmable-typemap-02-scanner branch 8 times, most recently from 2388cf1 to e92938eCompareFebruary 16, 2026 21:04
@simonrozsival
simonrozsivalforce-pushed the dev/simonrozsival/trimmable-typemap-03-generators branch from 4e7d06c to 7088777CompareFebruary 16, 2026 22:25
@simonrozsival
simonrozsivalforce-pushed the dev/simonrozsival/trimmable-typemap-02-scanner branch from e92938e to 9215de9CompareFebruary 18, 2026 15:49
@simonrozsival
simonrozsivalforce-pushed the dev/simonrozsival/trimmable-typemap-03-generators branch from 7088777 to b9f33c9CompareFebruary 18, 2026 16:31
@simonrozsival
simonrozsival changed the base branch from dev/simonrozsival/trimmable-typemap-02-scanner to mainFebruary 18, 2026 16:31
@simonrozsivalsimonrozsival changed the title [TrimmableTypeMap] Add TypeMap assembly and JCW Java source generators[TrimmableTypeMap] Add TypeMap assembly generators with model builderFeb 18, 2026
@simonrozsival
simonrozsivalforce-pushed the dev/simonrozsival/trimmable-typemap-03-generators branch from b9f33c9 to 21a68dbCompareFebruary 18, 2026 17:08
simonrozsivaland others added 4 commits March 10, 2026 15:08
Dispose PEReader (which owns the file stream) if GetMetadataReader()
or Build() throws an exception during index creation.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Replace string interpolation cache key in ExtendsJavaPeer with
(string, string) tuple to avoid allocation per recursive call
- Add single-use guard: throw if Scan() is called more than once,
making the stateful contract explicit
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Types like My.Type and My_Type would both produce My_Type_Proxy after
character replacement. Track used names and append a numeric suffix
if a collision is detected.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Document the intentional behavior that TypeMapAssociation entries
are not emitted when neither peer in an alias group has a proxy.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@simonrozsival
simonrozsivalforce-pushed the dev/simonrozsival/trimmable-typemap-03-generators branch from 1755b67 to a5296b1CompareMarch 10, 2026 14:09
simonrozsivaland others added 4 commits March 10, 2026 15:27
- Fix FullPipeline_AllFixtures_TypeMapAttributeCountMatchesEntries to
include model.Associations.Count in the expected total. The old
formula was accidentally correct (zero associations in fixtures).
Add a dedicated alias-group test that exercises the corrected count.
- Fix EmitBody to capture the sig blob handle before running the
emitIL callback, preventing signature corruption if the callback
calls AddMemberRef (which reuses the same scratch BlobBuilder).
Add regression test that triggers the reentrance scenario.
- Add Emit_CalledTwice_Throws test documenting that MetadataBuilder
already prevents double Emit on the same emitter instance.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…mpty<T>()
- Replace null-forgiving ! with OfType<string>() in ModelBuilder.cs
- Replace null-forgiving ! with explicit null check in TypeMapAssemblyEmitter.cs
- Replace null-forgiving ! with ?? throw in FixtureTestBase.cs
- Replace Array.Empty<T>() with [] in 5 test file locations
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ction
JavaInterop-style activation constructors:
- Add EmitBody overload with local variable support to PEAssemblyBuilder
- Add JniObjectReference/JniObjectReferenceOptions type and member refs
- Emit IL that converts (IntPtr, JniHandleOwnership) to
(ref JniObjectReference, JniObjectReferenceOptions) for both leaf and
inherited ctor cases
Implementor/EventDispatcher detection:
- Move detection from name-based heuristic in ModelBuilder to scanner
- Add IsImplementorOrEventDispatcher flag to JavaPeerInfo
- Use JNI name prefix 'mono/' + managed name suffix to avoid
misclassifying user types ending in 'Implementor'
- Update test fixtures to use realistic 'mono/' JNI name prefix
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Remove the IsImplementorOrEventDispatcher detection from scanner and
ModelBuilder. These types will be treated as unconditional ACW types
for now (same behavior as the legacy type map).
A dedicated follow-up issue will explore proper trimming of Implementor
and EventDispatcher types with appropriate analysis and testing.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
simonrozsivaland others added 7 commits March 10, 2026 18:00
Restores code that was already present in the scanner PR stack
(dev/simonrozsival/trimmable-typemap-02-scanner) but was mistakenly
removed during the PR slim-down (commit 2aca005). This code is
pre-existing groundwork for [Export] support, not new to this PR.
Restored:
- MarshalMethodInfo record on JavaPeerInfo
- CompatJniName property on JavaPeerInfo
- ExportInfo record on AssemblyIndex
- CollectMarshalMethods, ParseExportAttribute, and related scanner methods
- BuildJniSignatureFromManaged / ManagedTypeToJniDescriptor helpers
- ComputeAutoJniNames (dual JNI naming for acw-map.txt compat)
- Marshal method, Export, and CompatJniName tests
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Restore all Scanner/*.cs files to their original scanner branch versions
(dev/simonrozsival/trimmable-typemap-02-scanner). This PR should only add
new code on top of the scanner, not refactor it.
Changes vs scanner branch (additions only):
- JavaPeerInfo: add ManagedTypeNamespace, ManagedTypeShortName properties
- JavaPeerScanner: populate new properties + ExtractNamespace/ExtractShortName
- AssemblyIndex: fix file handle leak in Create (try-catch on error)
Reverts all gratuitous style changes (class→record, set→init, required,
string interpolation, primary constructors, etc.) that made the diff
against the scanner branch unnecessarily large.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The scanner PR was already merged to main with record/required/init
style. Previous commit mistakenly restored from the stale local scanner
branch, causing regressions (record→class, init→set, etc.) visible in
the GitHub PR diff.
Now scanner files match origin/main exactly, with only +27 lines added:
- JavaPeerInfo: ManagedTypeNamespace, ManagedTypeShortName properties
- JavaPeerScanner: populate new properties + ExtractNamespace/ExtractShortName
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…cleanup
- Fix missing ELEMENT_TYPE_BYREF for ref JniObjectReference in JI-style
activation ctor signature (MissingMethodException at runtime)
- Make DeterministicMvid content-aware so different type maps produce
different MVIDs even with the same module name
- Fix ExtractNamespace for nested types (My.NS.Outer+Inner -> My.NS)
- Add JNIEnv.DeleteRef(handle, ownership) call after JI-style ctor to
match legacy TypeManager.CreateProxy handle cleanup behavior
- Always include Mono.Android in IgnoresAccessChecksTo for DeleteRef access
- Add unit tests covering all four fixes
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…on size
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

@jonathanpeppersjonathanpeppers left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

My AI code reviewer says this, but I don't think we need to take any action except look at the yaml changes:

 PR #10808 Review Summary
Title: [TrimmableTypeMap] Add TypeMap proxy groundwork with CreateInstance
Author: @simonrozsival | +2956 / -88 across 20 files | 43 commits
What it does
Adds the core code generation pipeline that transforms scanned JavaPeerInfo records into per-assembly TypeMap .dll assemblies containing proxy types with CreateInstance methods. Pipeline: Scanner →
ModelBuilder → Emitter → PE output.
Architecture (excellent)
Clean 3-layer separation:
- ModelBuilder — all decision logic (dedup, alias detection, ACW filtering, 2-arg vs 3-arg classification). Pure data in, pure data out.
- TypeMapAssemblyEmitter — mechanical IL emission from the model. No business logic.
- PEAssemblyBuilder — shared plumbing (MetadataBuilder, assembly refs, scratch BlobBuilders, PE serialisation). Reused by both per-assembly and root generators.
Findings
┌───┬────────────┬─────────────────────────────────────┬─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ # │ Severity │ File │ Issue │
├───┼────────────┼─────────────────────────────────────┼─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ 1 │ Medium │ JavaPeerInfo.cs │ ManagedTypeNamespace and ManagedTypeShortName are added and populated by the scanner, but not consumed by any production code in this PR. The │
│ │ │ │ ModelBuilder and emitter don't reference them. Likely for follow-up PRs (UCO wrappers / RegisterNatives), but this is speculative code shipping │
│ │ │ │ ahead of its consumer. │
├───┼────────────┼─────────────────────────────────────┼─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ 2 │ Low │ TypeMapAssemblyData.cs │ Contains 6 types in one file (170 lines). The types are cohesive model records, but one-type-per-file is a strict convention in this org. Could │
│ │ │ │ split into TypeMapAttributeData.cs, JavaPeerProxyData.cs, TypeRefData.cs, ActivationCtorData.cs, TypeMapAssociationData.cs. │
├───┼────────────┼─────────────────────────────────────┼─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ 3 │ Low │ TypeMapAssemblyGeneratorTests.cs, │ Missing newline at end of file (\ No newline at end of file). │
│ │ │ TypeMapModelBuilderTests.cs │ │
└───┴────────────┴─────────────────────────────────────┴─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
Positives
- Deterministic output: SortedDictionary for JNI name ordering, sorted aliases, content-fingerprinted MVID — builds are reproducible.
- PEAssemblyBuilder scratch BlobBuilders: Reuses _sigBlob/_codeBlob/_attrBlob per method, avoiding per-emission allocations. Correctly captures the sig blob handle before emitIL callbacks (which can call
AddMemberRef and clear _sigBlob). There's even a regression test (EmitBody_ILCallbackCallsAddMemberRef_SignatureNotCorrupted) for this.
- 141 tests covering model builder, PE generation round-trips, root assembly, scanner behavior, and edge cases (generics, nested types, aliases, JI-style ctors, invoker exclusion).
- Test infrastructure refactoring: Extracted FixtureTestBase with Lazy<> cached fixture scanning — scanner tests and generator tests share the same fixture without re-scanning.
- IL emission comments map to pseudo-C# equivalents, making the ECMA-335 byte-level code readable.
- 5 CreateInstance strategies properly handled: leaf newobj, inherited (GetUninitializedObject), invoker, JI-style (with JniObjectReference conversion + DeleteRef cleanup), generic definition (throws), and
no-activation (returns null).
Summary
This is a well-structured, well-tested foundation PR. The main substantive concern is the two unused JavaPeerInfo properties shipping without consumers. The rest is clean.

Comment threadbuild-tools/automation/azure-pipelines-public.yaml Outdated
The -m:2 flag was added during debugging and is not needed on Linux
which doesn't have file locking issues like Windows.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@jonathanpeppers
jonathanpeppers merged commit bf17a4a into mainMar 11, 2026
6 checks passed
@jonathanpeppers
jonathanpeppers deleted the dev/simonrozsival/trimmable-typemap-03-generators branch March 11, 2026 16:59
simonrozsival added a commit that referenced this pull request Mar 11, 2026
… helpers
Add the UCO (UnifiedCallableObject) wrapper generation, JCW (Java Callable
Wrapper) Java source generator, and JNI signature parsing utilities on top
of the TypeMap proxy groundwork from PR #10808.
### Generator additions
- JcwJavaSourceGenerator: generates .java source files for JCW types
- JniSignatureHelper: JNI signature parsing (parameter types, return types,
CLR type encoding)
- Extended ModelBuilder with UCO method/constructor handling
- Extended TypeMapAssemblyEmitter with UCO wrapper IL emission
### Scanner enrichment
- JavaPeerInfo: added BaseJavaName, ImplementedInterfaceJavaNames,
JavaConstructors, JniParameterInfo for downstream generators
- JavaPeerScanner: resolver methods for base types, interfaces, constructors
### Tests
- 219 tests covering all generators, model builder scenarios, scanner
behavior, and edge cases
- Test fixtures extended with ExportWithThrows type
Rebased on main after PR #10808 merge — adapted test code to use init-only
record patterns (with {}) and added missing required members.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
simonrozsival added a commit that referenced this pull request Mar 11, 2026
Proactively address feedback patterns from the #10808 code review:
- Remove null! from TypeMapAssemblyEmitter: make _pe readonly, init in ctor
- Fix misleading JniParamKind.Boolean comment (Z → bool, not sbyte)
- Make AddActivationCtorRef style-aware: support both XamarinAndroid
(IntPtr, JniHandleOwnership) and JavaInterop (ref JniObjectReference,
JniObjectReferenceOptions) activation constructor styles
- Remove default parameter values from MakeInterfacePeer test helper
- Replace disk I/O with MemoryStream in generator tests: add Stream
overloads to Emit/Generate, use MemoryStream in tests for faster
and more reliable test execution
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
simonrozsival added a commit that referenced this pull request Mar 12, 2026
Tests were accidentally dropped during the rebase refactoring:
- EmitterBehavior: Emit_CalledTwice_Throws, signature blob corruption
- DeterminismTests: different/identical content MVIDs
- JavaInteropActivation: JI-style ctor type refs, byref param, DeleteRef
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
simonrozsival added a commit that referenced this pull request Mar 12, 2026
… helpers
Add the UCO (UnifiedCallableObject) wrapper generation, JCW (Java Callable
Wrapper) Java source generator, and JNI signature parsing utilities on top
of the TypeMap proxy groundwork from PR #10808.
### Generator additions
- JcwJavaSourceGenerator: generates .java source files for JCW types
- JniSignatureHelper: JNI signature parsing (parameter types, return types,
CLR type encoding)
- Extended ModelBuilder with UCO method/constructor handling
- Extended TypeMapAssemblyEmitter with UCO wrapper IL emission
### Scanner enrichment
- JavaPeerInfo: added BaseJavaName, ImplementedInterfaceJavaNames,
JavaConstructors, JniParameterInfo for downstream generators
- JavaPeerScanner: resolver methods for base types, interfaces, constructors
### Tests
- 219 tests covering all generators, model builder scenarios, scanner
behavior, and edge cases
- Test fixtures extended with ExportWithThrows type
Rebased on main after PR #10808 merge — adapted test code to use init-only
record patterns (with {}) and added missing required members.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
simonrozsival added a commit that referenced this pull request Mar 12, 2026
Proactively address feedback patterns from the #10808 code review:
- Remove null! from TypeMapAssemblyEmitter: make _pe readonly, init in ctor
- Fix misleading JniParamKind.Boolean comment (Z → bool, not sbyte)
- Make AddActivationCtorRef style-aware: support both XamarinAndroid
(IntPtr, JniHandleOwnership) and JavaInterop (ref JniObjectReference,
JniObjectReferenceOptions) activation constructor styles
- Remove default parameter values from MakeInterfacePeer test helper
- Replace disk I/O with MemoryStream in generator tests: add Stream
overloads to Emit/Generate, use MemoryStream in tests for faster
and more reliable test execution
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
simonrozsival added a commit that referenced this pull request Mar 12, 2026
Tests were accidentally dropped during the rebase refactoring:
- EmitterBehavior: Emit_CalledTwice_Throws, signature blob corruption
- DeterminismTests: different/identical content MVIDs
- JavaInteropActivation: JI-style ctor type refs, byref param, DeleteRef
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

Area: CoreCLRIssues that only occur when using CoreCLR.Area: NativeAOTIssues that only occur when using NativeAOT.copilot`copilot-cli` or other AIs were used to author thistrimmable-type-map

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@simonrozsival@jonathanpeppers