Skip to content

[TrimmableTypeMap] Java peer scanner with SRM + test project #10798

@simonrozsival

Description

@simonrozsival

Part of #10789

Overview

Implement the Java peer type scanner using System.Reflection.Metadata (SRM) — no Mono.Cecil. This is the foundation that all code generation builds on.

New project: src/Microsoft.Android.Sdk.TrimmableTypeMap/ — a dedicated assembly separate from Xamarin.Android.Build.Tasks for fast inner dev loop.

What this includes

Scanner (JavaPeerScanner)

  • Open assemblies via PEReader + MetadataReader
  • Find types with [Register] attribute → extract JNI name and DoNotGenerateAcw flag
  • Detect activation constructors: XI style (IntPtr, JniHandleOwnership) first, then JI style (ref JniObjectReference, JniObjectReferenceOptions) — walk up hierarchy if not on type itself
  • Collect marshal methods: methods with [Register(name, sig, connector)] or [Export] attributes
  • Detect component attributes ([Activity], [Service], [BroadcastReceiver], [ContentProvider], [Application], [Instrumentation]) for unconditional marking
  • Detect interface implementors and invoker types (including generic interface instantiations)
  • Handle generic types: open generic definition gets the TypeMap entry
  • Handle [Application(BackupAgent=typeof(...))] and [Application(ManageSpaceActivity=typeof(...))] cross-references → force unconditional
  • Support IJniNameProviderAttribute for custom JNI name resolution
  • Compute CompatJniName (lowercased namespace) for acw-map.txt backward compatibility

Two-phase in-memory architecture (critical for performance)

Both PoCs suffered from O(n²) behavior scanning Mono.Android (~8000 types). The scanner uses:

Phase 1 — Index building (one pass per assembly):

  • TypesByFullName / TypesByJniName dictionaries for O(1) lookups
  • RegisterInfoByType / AttributesByType pre-indexed per type
  • IJniNameProviderAttribute detection and reclassification across assemblies
  • All state stays in memory — MSBuild Inputs/Outputs handles target-level incrementality

Phase 2 — Type analysis (uses cached indices only):

  • ActivationCtorCache — hierarchy walk results shared across types with same base
  • extendsJavaPeerCache — caches ExtendsJavaPeer results and doubles as cycle detection
  • Cross-assembly type resolution via TryResolveType / ResolveRegisterJniName helpers

Performance target: <1s for Mono.Android (~8000 types).

CRC64 polynomial

The scanner uses System.IO.Hashing.Crc64 (ECMA polynomial) instead of the legacy Crc64Helper (Jones polynomial). This is intentional and safe. The CRC64 hash only needs to be self-consistent within a single compilation — the scanner computes the JNI name, the JCW generator uses it, the typemap records it, and acw-map.txt maps it. All consumers read from the same scan pass. See implementation update comment for details.

Data model

JavaPeerInfo {
    JavaName, CompatJniName,
    ManagedTypeName, ManagedTypeNamespace, ManagedTypeShortName,
    AssemblyName, BaseJavaName, ImplementedInterfaceJavaNames,
    IsInterface, IsAbstract, DoNotGenerateAcw,
    IsUnconditional, IsGenericDefinition,
    MarshalMethods[], JavaConstructors[],
    ActivationCtor, InvokerTypeName?
}

MarshalMethodInfo {
    JniName, JniSignature, Connector,
    ManagedMethodName, DeclaringTypeName, DeclaringAssemblyName,
    NativeCallbackName, Parameters[], JniReturnType,
    IsConstructor, ThrownNames, SuperArgumentsString
}

Test projects

Unit tests (tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/):

  • Hand-crafted test assembly (~20 types) for TDD inner loop
  • 75 tests covering scanner logic, JNI parsing, edge cases

Integration tests (tests/Microsoft.Android.Sdk.TrimmableTypeMap.IntegrationTests/):

  • Legacy comparison tests: Run both legacy Cecil-based scanner and new SRM scanner on same assemblies, compare results
  • Real Mono.Android.dll (~8000 types) side-by-side comparison
  • UserTypesFixture.dll — user-type edge cases ([Activity], [Service], [Export], nested types, generic types, plain Java.Lang.Object subclasses)
  • 10 tests (type maps + marshal methods for both assemblies)

Type classification rules (spec §15.9)

Type Unconditional? Gets JCW? Gets RegisterNatives?
User class with [Activity]/[Service]/etc
Custom view from layout XML
User class without component attr Trimmable
MCW binding (DoNotGenerateAcw=true) Trimmable
Interface Trimmable
Invoker Excluded

Metadata

Metadata

Assignees

Labels

Area: CoreCLRIssues that only occur when using CoreCLR.Area: NativeAOTIssues that only occur when using NativeAOT.needs-triageIssues that need to be assigned.trimmable-type-map

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions