Skip to content

[TrimmableTypeMap] Build pipeline + manifest generation#10980

Draft
simonrozsival wants to merge 32 commits intodev/simonrozsival/trimmable-typemap-runtime-prfrom
dev/simonrozsival/trimmable-typemap-build-pipeline
Draft

[TrimmableTypeMap] Build pipeline + manifest generation#10980
simonrozsival wants to merge 32 commits intodev/simonrozsival/trimmable-typemap-runtime-prfrom
dev/simonrozsival/trimmable-typemap-build-pipeline

Conversation

@simonrozsival
Copy link
Member

@simonrozsival simonrozsival commented Mar 20, 2026

Summary

Build pipeline fixes and manifest generation for the trimmable typemap feature. This PR builds on #10967 (runtime changes).

Build pipeline (original scope)

  • TypeMap assembly generation via GenerateTrimmableTypeMap task (SRM-based scanner + IL generator)
  • JCW Java source generation
  • ILLink integration (CoreCLR targets)
  • Native typemap stubs (LLVM IR)
  • _RemoveRegisterAttribute override
  • RuntimeHostConfigurationOption for TrimmableTypeMap + TypeMappingEntryAssembly
  • PreserveLists/Trimmable.CoreCLR.xml for JNIEnvInit.Initialize
  • HelloWorld sample with _AndroidTypeMapImplementation=trimmable

Manifest generation (#10807)

  • No Cecil — SRM-based JavaPeerScanner extended to capture component attributes
  • TrimmableManifestGenerator: data-driven property mapping (7 static arrays, 9 enum converters)
  • Type-level: [Activity], [Service], [BroadcastReceiver], [ContentProvider] with full properties, [IntentFilter], [MetaData]
  • Assembly-level: [Permission], [UsesPermission], [UsesFeature], [UsesLibrary], [UsesConfiguration], [Property], [Application]
  • MainLauncher intent-filter, runtime provider injection, template merging, deduplication
  • ManifestPlaceholders, CheckedBuild/debuggable, ApplicationJavaClass
  • XA4213 constructor validation, duplicate Application detection
  • 30 unit tests (130ms)

Files changed

Area Files
Scanner AssemblyIndex.cs, JavaPeerInfo.cs, JavaPeerScanner.cs
Generator TypeMapAssemblyEmitter.cs, GenerateTrimmableTypeMap.cs
Manifest TrimmableManifestGenerator.cs (new)
Targets Trimmable.targets, Trimmable.CoreCLR.targets
Native host.cc, host.hh, managed-interface.hh
Tests TrimmableManifestGeneratorTests.cs (new, 30 tests)
Other GenerateEmptyTypemapStub.cs, GenerateNativeApplicationConfigSources.cs, PreserveLists/Trimmable.CoreCLR.xml, ApplicationRegistration.Trimmable.java

Depends on

simonrozsival and others added 15 commits March 19, 2026 17:46
- JavaPeerProxy / JavaPeerProxy<T> — AOT-safe proxy attribute base
- IAndroidCallableWrapper — RegisterNatives(JniType) for ACW types
- JavaPeerContainerFactory<T> — AOT-safe array/list/collection/dict
- TypeMapException — error reporting

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Proxy types now extend JavaPeerProxy<T> instead of JavaPeerProxy.
TargetType and GetContainerFactory() are inherited from the generic
base. Generator references TrimmableTypeMap for ActivateInstance and
RegisterMethod.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add TrimmableTypeMap class with core typemap functionality:
TryGetType, TryCreatePeer, GetInvokerType, GetContainerFactory,
ActivateInstance.

Add TrimmableTypeMapTypeManager delegating to TrimmableTypeMap.

Rename ManagedValueManager to JavaMarshalValueManager. Add proxy-based
peer creation in TryConstructPeer via TrimmableTypeMap.TryCreatePeer.

Add RuntimeFeature.TrimmableTypeMap feature switch with ILLink
substitutions. Wire into JNIEnvInit (CoreCLR) and JavaInteropRuntime
+ JreRuntime (NativeAOT).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add registerNatives(Class) native method to mono.android.Runtime.java
so JCW static initializer blocks can trigger native method registration.

Add to TrimmableTypeMap:
- RegisterBootstrapNativeMethod() registers the JNI callback during init
- OnRegisterNatives() resolves the proxy and calls
  IAndroidCallableWrapper.RegisterNatives(JniType) to bind UCO ptrs
- RegisterMethod() helper for per-method registration (TODO: batch)

Wire RegisterBootstrapNativeMethod() call in JNIEnvInit after runtime
creation.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
When TrimmableTypeMap is available, use JavaPeerContainerFactory from
the proxy for IList<T>, ICollection<T>, IDictionary<K,V> marshaling
and array creation instead of MakeGenericType/Array.CreateInstance.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Ranks 1-3: direct new T[], T[][], T[][][] — fully AOT-safe.
Rank 4+: when dynamic code is supported (CoreCLR), falls back to
MakeArrayType + CreateInstanceFromArrayType. Throws on NativeAOT.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add missing using Android.Runtime for JniHandleOwnership
- Add DynamicallyAccessedMembers annotations required by JavaList<T>,
  JavaCollection<T>, JavaDictionary<K,V> on factory type parameters
- Fix IJniNameProviderAttribute lookup (not an Attribute, use
  GetCustomAttributes instead of GetCustomAttribute<T>)
- Fix JniType constructor (takes string, not JniObjectReference)
- Restore #pragma warning disable IL3050 for Array.CreateInstance
  fallback path
- Suppress IL2073 on GetInvokerType (invoker types preserved by
  MarkJavaObjects trimmer step)

Build succeeds locally.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Pass TrimmableTypeMap via constructor instead of settable property.
Use Interlocked.CompareExchange for single-instance safety. Keep
RegisterBootstrapNativeMethod as separate call (JNI runtime must be
initialized first).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Updated from macOS-7 CI build 13601673.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ypeNameFromClass

- ActivateInstance: use JNI GetObjectClass + GetJniTypeNameFromClass to find
  proxy type via the TypeMap dictionary instead of targetType.GetCustomAttribute
  (the self-application attribute is on the proxy, not the target)
- Pre-load per-assembly TypeMap DLLs via TypeMapAssemblyTarget attributes before
  GetOrCreateExternalTypeMapping (Android assembly store needs explicit probing)
- Fix GetJniTypeNameFromInstance → GetJniTypeNameFromClass in OnRegisterNatives
  (nativeClassHandle is a jclass, not a jobject)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- JNIEnvInit: wrap RegisterBootstrapNativeMethod in explicit
  'if (RuntimeFeature.TrimmableTypeMap)' guard instead of null-conditional
  operator, so the trimmer can eliminate it when the feature is disabled
- TryCreatePeer: look up proxy via JNI class name from the TypeMap
  dictionary instead of type.GetCustomAttribute (the self-application
  attribute is on the proxy type, not the managed target type)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Both RegisterAttribute and JniTypeSignatureAttribute implement
IJniNameProviderAttribute, so a single GetCustomAttributes check
covers both. Made the method internal+static so
TrimmableTypeMapTypeManager.GetSimpleReferences can reuse it
instead of duplicating the attribute lookup.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Build pipeline changes for the trimmable typemap, discovered during
end-to-end HelloWorld experiment.

Scanner/generator fixes:
- Derive UCO callback names from [Register] Connector field
  (e.g. 'GetOnCreate_Landroid_os_Bundle_Handler' → 'n_OnCreate_Landroid_os_Bundle_')
- Emit self-application [ProxyType] custom attribute on proxy types
  for GetCustomAttribute<JavaPeerProxy>() to work
- Use CRC64-Jones (Crc64Helper) for package name hashing to match
  the legacy JCW generator convention

Target restructuring (following PoC pattern):
- _GenerateJavaStubs (outer build): runs GenerateTrimmableTypeMap using
  @(ReferencePath) as input (available without inner builds)
- _AddTrimmableTypeMapAssembliesToStore (outer build, BeforeTargets=_BuildApkEmbed):
  adds per-ABI entries to _BuildApkResolvedUserAssemblies
- _RemoveRegisterAttribute override: simple copy to shrunk/

Task fixes:
- Removed IsMonoAndroidAssembly filter (ReferencePath items lack this metadata)
- Added framework assembly filter for JCW generation (user types only)
- GenerateNativeApplicationConfigSources: tolerate trimmed JNIEnvInit methods
- GenerateEmptyTypemapStub: LLVM IR symbols for libmonodroid.so

Native:
- C++ Host::Java_mono_android_Runtime_registerNatives no-op stub

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Demonstrates the trimmable typemap end-to-end. Requires two remaining
workarounds in trimmable-override.targets:
1. _TrimmerFeatureSettings for TrimmableTypeMap=true
2. _PatchBundledAssembliesSize (assembly store array sizing)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…flows correctly

RuntimeHostConfigurationOption with Trim='true' already flows to
_TrimmerFeatureSettings via the ILLink targets. The workaround was
unnecessary — only the assembly store array sizing hack remains.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@simonrozsival simonrozsival changed the title [TrimmableTypeMap] Build pipeline: targets, scanner, generator fixes [WIP][TrimmableTypeMap] Build pipeline: targets, scanner, generator fixes Mar 20, 2026
The CRC64-Jones change was unnecessary. System.IO.Hashing.Crc64 (CRC-64/XZ)
produces the same package name hash as the legacy JCW generator when given
the correct input assemblies. The previous mismatch was caused by the scanner
receiving incomplete assembly inputs (empty _ResolvedAssemblies), not by the
hash algorithm.

With the restructured targets using @(ReferencePath), the scanner sees all
assemblies and produces matching CRC hashes. Removed Crc64Helper shared source
files and AllowUnsafeBlocks from the csproj.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
<!-- Patch bundled assemblies array size for TypeMap DLLs -->
<Target Name="_PatchBundledAssembliesSize" AfterTargets="_GeneratePackageManagerJava"
Condition=" '$(_AndroidTypeMapImplementation)' == 'trimmable' ">
<Exec Command="sed -i '' 's/\[\([0-9]*\) x %struct.AssemblyStoreSingleAssemblyRuntimeData\]/[256 x %struct.AssemblyStoreSingleAssemblyRuntimeData]/g' $(IntermediateOutputPath)android/environment.*.ll"
Copy link
Member Author

Choose a reason for hiding this comment

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

this is pure evil 👀

Copy link
Member Author

Choose a reason for hiding this comment

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

Already removed in a prior commit ✅

simonrozsival and others added 6 commits March 20, 2026 13:32
TrimmableTypeMap and TypeMappingEntryAssembly feature switches are now
declared in Microsoft.Android.Sdk.TypeMap.Trimmable.targets, not in the
app csproj. The only user-facing property is _AndroidTypeMapImplementation.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Move _GenerateTrimmableTypeMap to AfterTargets=_ResolveAssemblies
- CoreCLR targets: add to ManagedAssemblyToLink + --typemap-entry-assembly
- Remove all custom descriptors (ILLink handles TypeMap natively)
- Minimal sample csproj (only _AndroidTypeMapImplementation)

ILLink processes TypeMap DLLs correctly (they appear in linked/).
Remaining issue: publish conflict resolution sees duplicate relative
paths for TypeMap DLLs in linked/ output (missing metadata).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Refactor: Initialize sets args->registerJniNativesFn when the legacy
path is active (RuntimeFeature.TrimmableTypeMap == false). When
TrimmableTypeMap is true, the field is null — the trimmable path
handles registration via RegisterBootstrapNativeMethod (managed).

This eliminates the create_delegate call for RegisterJniNatives,
allowing the trimmer to remove it in the trimmable path without
causing a native abort.

Also: fix typedef ordering in managed-interface.hh, guard the
jnienv_register_jni_natives call site for null.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…argets

- BeforeTargets=PrepareForILLink (not _ComputeManagedAssemblyToLink which
  doesn't exist in the Android SDK targets)
- Set DestinationSubPath on ManagedAssemblyToLink items
- Add _AddTrimmableTypeMapAssembliesToStore for assembly store injection
- Remove ErrorOnDuplicatePublishOutputFiles hack from sample

Remaining: JNIEnvInit.Initialize trimmed by ILLink when TypeMap DLLs
are processed. This is a pre-existing issue (native create_delegate
contract) that was masked by not processing TypeMap DLLs before.
Needs ILLink descriptor in Mono.Android for native UCO methods.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add PreserveLists/Trimmable.CoreCLR.xml with JNIEnvInit.Initialize
  (native entry point called via create_delegate)
- TrimmerRootDescriptor in shared Trimmable.targets
- Use PrepareForILLink hook (not _ComputeManagedAssemblyToLink which
  doesn't exist in Android SDK targets)
- DestinationSubPath on ManagedAssemblyToLink items resolves NETSDK1152
- Remove ErrorOnDuplicatePublishOutputFiles from sample csproj

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
ReferencePath and the app assembly are available after CoreCompile in
the outer build. Using AfterTargets=_ResolveAssemblies was wrong —
ReferencePath is empty at that point.

All 3 TypeMap DLLs now generated (_HelloTrimmable.TypeMap.dll,
_Mono.Android.TypeMap.dll, _Microsoft.Android.TypeMaps.dll).
JCW files generated and copied. ILLink processes TypeMap DLLs.
App runs on device (1.47s) with NO LinkerDescriptor.xml.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
_GenerateJavaStubs: lightweight — copies JCW files and generates stubs.
TypeMap generation already happened via _GenerateTrimmableTypeMap.
-->
<Target Name="_GenerateJavaStubs"
Copy link
Member Author

Choose a reason for hiding this comment

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

I don't know if this is the right name for this target. It is the same one we use it the LLVM IR typemap, but I think it's doing something else and that might be confusing. I think using a different name would be better.

Copy link
Member Author

Choose a reason for hiding this comment

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

We need to keep the name _GenerateJavaStubs because BuildOrder.targets references it in _PrepareBuildApkDependsOnTargets. Updated the comment to explain why the trimmable path reuses this target name (2fa1c2e).

simonrozsival and others added 2 commits March 20, 2026 16:50
Derives callback name from [Register] Connector field:
'GetOnCreate_Landroid_os_Bundle_Handler' → 'n_OnCreate_Landroid_os_Bundle_'

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Read trimmed TypeMap DLLs from linked/ (ILLink output) and add them to
both _ResolvedAssemblies (for assembly count) and _BuildApkResolvedUserAssemblies
(for assembly store with per-ABI metadata).

Removed:
- trimmable-override.targets (sed patch for assembly store sizing)
- Directory.Build.targets
- AdditionalAssemblyCount task property

Sample csproj is now completely clean — only _AndroidTypeMapImplementation.
App runs on device (743ms) with zero workarounds.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
<Target Name="_ConfigureTrimmableTypeMapForLinker"
BeforeTargets="PrepareForILLink;_RunILLink"
DependsOnTargets="_GenerateTrimmableTypeMap"
Condition=" '$(_AndroidTypeMapImplementation)' == 'trimmable' ">
Copy link
Member Author

Choose a reason for hiding this comment

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

this file would not be loaded if the type map implementation wasn't "trimmable", so this condition should be removed.

Copy link
Member Author

Choose a reason for hiding this comment

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

Already removed in 483dc03

<Target Name="_AddTrimmableTypeMapToLinker"
BeforeTargets="PrepareForILLink;_RunILLink"
DependsOnTargets="_GenerateTrimmableTypeMap"
Condition=" '$(_AndroidTypeMapImplementation)' == 'trimmable' ">
Copy link
Member Author

Choose a reason for hiding this comment

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

this file would not be loaded if the type map implementation wasn't "trimmable", so this condition should be removed.

Copy link
Member Author

Choose a reason for hiding this comment

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

Already removed in 483dc03

simonrozsival and others added 3 commits March 20, 2026 17:31
Replace duplicated per-ABI blocks with a single batched ItemGroup
derived from _BuildTargetAbis. This eliminates the copy-paste
duplication and uses MSBuild item batching over an ABI→RID mapping.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Remove the standalone HelloTrimmable sample and add the CoreCLR +
trimmable typemap properties to the existing HelloWorld sample instead.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…targets

The file is only imported when the condition is already true (gated
by the parent import in Xamarin.Android.Common.targets).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@simonrozsival simonrozsival changed the title [WIP][TrimmableTypeMap] Build pipeline: targets, scanner, generator fixes [TrimmableTypeMap] Build pipeline + manifest generation Mar 21, 2026
@simonrozsival simonrozsival force-pushed the dev/simonrozsival/trimmable-typemap-build-pipeline branch from e2214d6 to 483dc03 Compare March 21, 2026 06:54
@simonrozsival simonrozsival changed the title [TrimmableTypeMap] Build pipeline + manifest generation [TrimmableTypeMap] Build pipeline: targets, scanner, generator fixes Mar 21, 2026
@simonrozsival simonrozsival added copilot `copilot-cli` or other AIs were used to author this trimmable-type-map labels Mar 21, 2026
@simonrozsival simonrozsival force-pushed the dev/simonrozsival/trimmable-typemap-build-pipeline branch from 9475135 to 483dc03 Compare March 21, 2026 08:00
…tiveApplicationConfigSources

The trimmable _GenerateJavaStubs was missing:
1. ABI/RuntimeIdentifier metadata on TypeMap _ResolvedAssemblies items
   (required by CreateAssemblyStore)
2. GenerateNativeApplicationConfigSources call (builds assembly hash table)
3. _DefineBuildTargetAbis dependency (provides @(_BuildTargetAbis))

Now TypeMap DLLs flow through the full assembly pipeline and appear
in libassembly-store.so with correct hash entries.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@simonrozsival simonrozsival force-pushed the dev/simonrozsival/trimmable-typemap-build-pipeline branch from 12a1c2b to 4d55775 Compare March 21, 2026 09:12
…l path

MSBuild adds literal paths even when files don't exist. Use
_Microsoft.Android.TypeMap*.dll wildcard to avoid the issue.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@simonrozsival simonrozsival force-pushed the dev/simonrozsival/trimmable-typemap-runtime-pr branch from 76a490e to aad2ddb Compare March 21, 2026 09:14
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@simonrozsival simonrozsival changed the title [TrimmableTypeMap] Build pipeline: targets, scanner, generator fixes [TrimmableTypeMap] Build pipeline + manifest generation Mar 21, 2026
@simonrozsival simonrozsival force-pushed the dev/simonrozsival/trimmable-typemap-build-pipeline branch from 24e4399 to df43059 Compare March 21, 2026 10:13
…from typemap/

_AddTrimmableTypeMapAssembliesToStore now batches over @(_BuildTargetAbis)
to add TypeMap DLLs for each ABI. Falls back from linked/ (ILLink output)
to typemap/ (pre-trim) for Debug builds where ILLink doesn't run.

_GenerateJavaStubs sets _TypeMapFirstAbi/_TypeMapFirstRid properties and
passes TypeMap DLLs to GenerateNativeApplicationConfigSources via a local
item group (avoiding duplicate entries in _ResolvedAssemblies).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@simonrozsival simonrozsival force-pushed the dev/simonrozsival/trimmable-typemap-build-pipeline branch from df43059 to faa340e Compare March 21, 2026 10:39
…tyle

- Fix ConfigChangesToString: correct hex values for grammaticalGender (0x8000),
  fontWeightAdjustment (0x10000000), fontScale (0x40000000). Remove invalid 0x10000.
- Fix HasPublicParameterlessCtor: use MemberAccessMask to properly exclude
  protected/internal constructors (was incorrectly accepting them).
- Targets: use ->Count() for empty checks, Condition attribute first,
  add FileWrites for _TypeMapAssemblySource.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

copilot `copilot-cli` or other AIs were used to author this trimmable-type-map

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants