Skip to content

[TrimmableTypeMap] Fix trimmable Java object activation#11275

Open
simonrozsival wants to merge 2 commits into
mainfrom
trimmable-object-activation
Open

[TrimmableTypeMap] Fix trimmable Java object activation#11275
simonrozsival wants to merge 2 commits into
mainfrom
trimmable-object-activation

Conversation

@simonrozsival
Copy link
Copy Markdown
Member

@simonrozsival simonrozsival commented May 3, 2026

Summary

  • run default managed constructors from generated no-arg constructor callbacks using direct generated IL instead of reflection
  • mark generated constructor activation peers as replaceable so managed wrappers can replace temporary peers created during Java-side virtual dispatch
  • re-enable the throwable activation and JNI CreateInstance identity tests for trimmable typemap

Validation

  • dotnet build src/Mono.Android/Mono.Android.csproj -v:minimal -nologo -consoleloggerparameters:NoSummary
  • dotnet build src/Microsoft.Android.Sdk.TrimmableTypeMap/Microsoft.Android.Sdk.TrimmableTypeMap.csproj -v:minimal -nologo -consoleloggerparameters:NoSummary
  • MSBUILDDISABLENODEREUSE=1 /Users/simonrozsival/Projects/dotnet/android-trimmable-java-proxy-object/dotnet-local.sh build tests/Mono.Android-Tests/Mono.Android-Tests/Mono.Android.NET-Tests.csproj -c Debug -p:_AndroidTypeMapImplementation=trimmable -p:UseMonoRuntime=false -nr:false -m:1 -v:minimal -nologo -consoleloggerparameters:NoSummary
  • Focused device run with temporary category filter: passed=3, failed=0 for ActivatedDirectThrowableSubclassesShouldBeRegistered, GetObject_ReturnsMostDerivedType, JnienvCreateInstance_RegistersMultipleInstances

Related issues

@simonrozsival simonrozsival changed the title Fix trimmable Java object activation [TrimmableTypeMap] Fix trimmable Java object activation May 3, 2026
@simonrozsival simonrozsival added copilot `copilot-cli` or other AIs were used to author this trimmable-type-map labels May 3, 2026
@simonrozsival simonrozsival force-pushed the trimmable-virtual-constructor branch from 711aeae to 7e2737b Compare May 4, 2026 18:23
@simonrozsival simonrozsival force-pushed the trimmable-object-activation branch from a8491be to 3bcb461 Compare May 4, 2026 18:23
@simonrozsival simonrozsival force-pushed the trimmable-virtual-constructor branch from 7e2737b to dc13b42 Compare May 4, 2026 18:31
@simonrozsival simonrozsival force-pushed the trimmable-object-activation branch 2 times, most recently from 3bec3d2 to ddcddd4 Compare May 11, 2026 17:29
@simonrozsival simonrozsival changed the base branch from trimmable-virtual-constructor to main May 11, 2026 17:29
@simonrozsival simonrozsival marked this pull request as ready for review May 11, 2026 17:32
Copilot AI review requested due to automatic review settings May 11, 2026 17:32
@simonrozsival simonrozsival force-pushed the trimmable-object-activation branch from ddcddd4 to daeb39a Compare May 11, 2026 17:39
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR updates the TrimmableTypeMap pipeline to improve Java→managed activation behavior (notably around constructor callbacks), marks certain activation peers as replaceable to support Java-side virtual dispatch scenarios, and re-enables previously excluded device tests as those behaviors are restored.

Changes:

  • Updates typemap proxy/UCO emission to avoid reflection for default managed constructors and to mark activation peers as Replaceable.
  • Extends the typemap scanner/model to treat JniAddNativeMethodRegistrationAttribute types (hand-written Java peers) as requiring ACW/proxy support.
  • Adjusts test infrastructure and test inputs (C#/Java) to run trimmable-specific Java.Interop constructor-virtual-call scenarios and to remove now-unneeded exclusions.

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
tests/Mono.Android-Tests/Mono.Android-Tests/Xamarin.Android.RuntimeTests/NUnitInstrumentation.cs Removes trimmable exclusions for tests that are now expected to pass.
tests/Mono.Android-Tests/Mono.Android-Tests/Java.Lang/ObjectTest.cs Removes an outdated TODO tied to trimmable typemap behavior.
tests/Mono.Android-Tests/Mono.Android-Tests/Java.Interop/JnienvTest.cs Cleans up throwable activation test logging; adds TODO for open generic activation behavior.
tests/Mono.Android-Tests/Java.Interop-Tests/Java.Interop-trimmable/CallVirtualFromConstructorDerived.cs Adds a trimmable-specific managed peer used for constructor virtual-dispatch testing.
tests/Mono.Android-Tests/Java.Interop-Tests/Java.Interop-Tests.targets Adjusts Java test-jar inputs so trimmable builds use java-trimmable sources for specific classes.
tests/Mono.Android-Tests/Java.Interop-Tests/Java.Interop-Tests.NET.csproj Conditionally swaps managed CallVirtualFromConstructorDerived source between desktop and trimmable variants.
tests/Mono.Android-Tests/Java.Interop-Tests/java-trimmable/net/dot/jni/test/CallVirtualFromConstructorDerived.java Adds trimmable Java peer for derived constructor-virtual-call scenario.
tests/Mono.Android-Tests/Java.Interop-Tests/java-trimmable/net/dot/jni/test/CallVirtualFromConstructorBase.java Adds trimmable Java peer base class for the same scenario.
src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/TrimmableTypeMapBuildTests.cs Expands SDK pack assertions to ensure obsolete java-runtime artifacts aren’t shipped.
src/Mono.Android/Java.Interop/JavaPeerProxy.cs Adds helper to mark activation peers as Replaceable.
src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerScanner.cs Records presence of JniAddNativeMethodRegistrationAttribute for downstream model decisions.
src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerInfo.cs Adds model flag for JniAddNativeMethodRegistrationAttribute usage.
src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs Changes proxy/UCO emission logic (default ctor activation path + replaceable marking; also refactors typemap attribute emission).
src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ModelBuilder.cs Ensures handwritten Java peers with native registrations can trigger proxy/ACW generation.
src/java-runtime/java-runtime.targets Deletes/cleans obsolete java-runtime artifacts to prevent stale outputs and packaging.
Comments suppressed due to low confidence (3)

src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs:184

  • EmitCore() no longer emits per-rank __ArrayMapRank{N} sentinel TypeDefs (previously via EmitRankSentinels(model)), but ModelBuilder can still produce array entries (TypeMapAttributeData.AnchorRank / model.MaxArrayRank) and the root typemap loader expects those anchors to exist for array lookups. This will break TrimmableTypeMap.Instance.TryGetArrayType(...) / JNIEnv array creation when _AndroidTrimmableTypeMapMaxArrayRank > 0. Consider restoring rank-sentinel emission (and the corresponding per-rank TypeMap grouping) or removing/rewriting the array-entry pipeline end-to-end so the root loader and runtime agree.
			throw new ArgumentNullException (nameof (model));
		}
		if (stream is null) {
			throw new ArgumentNullException (nameof (stream));
		}

		EmitCore (model, useSharedTypemapUniverse);
		_pe.WritePE (stream);
	}

	void EmitCore (TypeMapAssemblyData model, bool useSharedTypemapUniverse)
	{
		_pe.EmitPreamble (model.AssemblyName, model.ModuleName, MetadataHelper.ComputeContentFingerprint (model));

		_javaInteropRef = _pe.AddAssemblyRef ("Java.Interop", new Version (0, 0, 0, 0));

		EmitTypeReferences ();
		if (useSharedTypemapUniverse) {

src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs:1402

  • EmitTypeMapAttribute() now ignores entry.AnchorRank and always uses the default anchor’s TypeMapAttribute<TGroup> ctor. Any array entries created with AnchorRank (see TypeMapAttributeData.AnchorRank) will end up in the wrong typemap universe/group, so the root loader’s per-rank TypeMapping.GetOrCreateExternalTypeMapping<__ArrayMapRankN>() maps won’t contain them. Either reintroduce the per-rank anchor handling here, or stop generating AnchorRank entries (and update the runtime/root loader accordingly).
					encoder.Token (nameFields [i]);

					// byte* signature
					encoder.OpCode (ILOpCode.Ldsflda);
					encoder.Token (sigFields [i]);

					// IntPtr functionPointer
					encoder.OpCode (ILOpCode.Ldftn);
					encoder.Token (validRegs [i].Wrapper);

					// Construct the struct on the evaluation stack and store it
					// at the destination address. This matches the Roslyn pattern:
					//   newobj JniNativeMethod::.ctor(byte*, byte*, IntPtr)
					//   stobj  JniNativeMethod

src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs:1008

  • For proxy.IsGenericDefinition, EmitUcoConstructor() now emits a no-op wrapper that silently returns. This changes behavior from “fail activation” to “succeed without a managed peer”, which can leave an allocated Java instance without a corresponding managed wrapper and also conflicts with existing expectations that open generic activation is unsupported (e.g., JnienvTest.NewOpenGenericTypeThrows). It would be safer to preserve the previous behavior of throwing a NotSupportedException (or otherwise surfacing an error) so the failure is explicit and debuggable.
		encoder.Call (_waitForBridgeProcessingRef);
		encoder.MarkLabel (tryStart);
		emitCallback (encoder);
		if (!isVoid) {
			encoder.StoreLocal (0);
		}
		encoder.Branch (ILOpCode.Leave, afterAll);

		encoder.MarkLabel (catchStart);
		encoder.StoreLocal (isVoid ? 0 : 1);

@simonrozsival simonrozsival marked this pull request as draft May 11, 2026 17:43
@simonrozsival simonrozsival force-pushed the trimmable-object-activation branch from daeb39a to 50e081e Compare May 11, 2026 17:44
simonrozsival and others added 2 commits May 11, 2026 19:54
Run default managed constructors for generated no-arg constructor callbacks, and mark generated constructor activation peers as replaceable so managed wrappers can replace temporary peers created during Java-side virtual dispatch.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Avoid reflection for generated no-arg Java constructor callbacks by emitting IL that attaches the JNI peer and invokes the managed default constructor directly.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@simonrozsival simonrozsival force-pushed the trimmable-object-activation branch from 50e081e to 02e0cf4 Compare May 11, 2026 17:55
@simonrozsival simonrozsival marked this pull request as ready for review May 11, 2026 17:56
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