Skip to content

Reduce generated JNI callback IL size#1452

Open
simonrozsival wants to merge 7 commits into
mainfrom
copilot/issue-1359-investigation
Open

Reduce generated JNI callback IL size#1452
simonrozsival wants to merge 7 commits into
mainfrom
copilot/issue-1359-investigation

Conversation

@simonrozsival

@simonrozsival simonrozsival commented Jun 9, 2026

Copy link
Copy Markdown
Member

Fixes #1359

Summary

This reduces generated JNI callback IL size by moving the repeated marshal-boundary wrapper code out of every generated callback and into generated helper overloads.

Generated callbacks now have two pieces:

  • n_*: a small native-entry thunk that forwards JNI arguments to a generated safe invoker.
  • __n_*: the actual callback body that performs GetObject, argument conversion, user call/property access, cleanup, and return marshaling.

Java.Interop.JniMarshal now provides shared SafeInvokeAction and SafeInvokeFunc overloads. These helpers centralize:

  • JniEnvironment.BeginMarshalMethod(...)
  • try / catch (Exception) / finally
  • JniRuntime.OnUserUnhandledException(...)
  • JniEnvironment.EndMarshalMethod(...)

The helper shape is intentionally C#-only and uses managed function pointers with the function pointer as the final argument:

Java.Interop.JniMarshal.SafeInvokeAction (jnienv, native__this, arg0, arg1, &__n_Foo);
Java.Interop.JniMarshal.SafeInvokeFunc (jnienv, native__this, arg0, &__n_Bar);

Design notes

During prototyping I tested several alternatives:

  • explicit IL tail. thunks
  • C# forwarding patterns that might encourage tailcalls
  • NativeAOT output for C# forwarding patterns
  • function pointer first vs. function pointer last

The useful findings were:

  • C# and NativeAOT do not reliably emit tailcalls for these forwarding patterns.
  • Explicit tail. can help low-arity compiled IL thunks, but has severe arity/ABI cliffs. On arm64, mixed signatures around 8+ args fell back to helper-based tailcalls and became ~4x slower than normal calls.
  • Function-pointer-last preserves JNI argument register/stack placement better than function-pointer-first.

So this PR deliberately avoids explicit tail. and uses the safer C# helper shape with JNI args first and fnptr last. The safe invokers live in Java.Interop.JniMarshal rather than generated binding output, so each binding assembly only emits the small n_*/__n_* split and does not get its own copy of the helper methods.

DebuggerDisableUserUnhandledExceptions placement

This PR does not remove [global::System.Diagnostics.DebuggerDisableUserUnhandledExceptions] from the marshal exception boundary. It moves the attribute to the shared Java.Interop.JniMarshal.SafeInvokeAction / SafeInvokeFunc helpers because those helpers now own the try / catch (Exception) block and call OnUserUnhandledException(...).

The generated n_* methods are now only forwarding thunks and no longer catch user exceptions. Keeping the attribute on every n_* thunk would be redundant for debugger behavior and would add back per-callback metadata/IL that this change is trying to remove. The attribute follows the catch block.

Mono.Android size measurements

Measured by building Mono.Android from a dotnet/android worktree with this Java.Interop generator patch applied.

The shared-helper version has no generated __JniMarshalMethodHelper copies (0 matches in generated mcw output). Generated callbacks call Java.Interop.JniMarshal.SafeInvoke* instead (29,967 call sites in Mono.Android generated output).

Release

Artifact Baseline Patched Delta
Runtime Mono.Android.dll 42,798,592 42,121,728 -676,864 bytes
Ref Mono.Android.dll 19,295,744 19,339,776 +44,032 bytes
Mono.Android.pdb 32,553,308 30,690,252 -1,863,056 bytes
Generated mcw .cs total 29,234,484 28,508,893 -725,591 bytes

Debug

Artifact Baseline Patched Delta
Runtime Mono.Android.dll 46,168,064 45,507,584 -660,480 bytes
Ref Mono.Android.dll 19,294,720 19,339,264 +44,544 bytes
Mono.Android.pdb 38,057,736 35,815,552 -2,242,184 bytes
Generated mcw .cs total 29,234,484 28,508,893 -725,591 bytes

Compared with the earlier generated-helper prototype, moving the helper into Java.Interop.JniMarshal saves an additional ~11.5 KiB in runtime Mono.Android.dll, ~8.5 KiB in ref Mono.Android.dll, and ~6.8 KiB of generated mcw source.

Measurement notes are saved locally at:

~/.copilot/session-state/fd1f37cb-e759-40da-8b40-c668a63336f6/files/android-issue-1359-shared-helper-measure-summary.txt

Validation

  • dotnet build tools/generator/generator.csproj -v:minimal
  • dotnet test tests/generator-Tests/generator-Tests.csproj -v:minimal
    • 455 passed, 0 failed

Copilot AI review requested due to automatic review settings June 9, 2026 13:37

Copilot AI left a comment

Copy link
Copy Markdown

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 generator to reduce emitted IL for JNI callbacks by moving the repeated marshal-boundary wrapper (Begin/EndMarshalMethod + try/catch/finally) into generated __JniMarshalMethodHelper.SafeInvokeAction/SafeInvokeFunc overloads, and splitting each callback into a small n_* thunk plus a __n_* callback body.

Changes:

  • Refactor generated callbacks so n_* becomes a minimal unsafe thunk that forwards to __JniMarshalMethodHelper with a managed function pointer to __n_*.
  • Add generated __JniMarshalMethodHelper overloads in __NamespaceMapping__.cs (XAJavaInterop1) to centralize marshal-boundary handling.
  • Update generator expected outputs to reflect the new callback shapes and helper emission.
Show a summary per file
File Description
tools/generator/SourceWriters/MethodCallback.cs Splits callback generation into thunk + body and routes through __JniMarshalMethodHelper.
tools/generator/SourceWriters/Attributes/DebuggerDisableUserUnhandledExceptionsAttributeAttr.cs Removes now-unneeded attribute writer (attribute applied on helper methods instead).
tools/generator/Java.Interop.Tools.Generator.ObjectModel/NamespaceMapping.cs Emits __JniMarshalMethodHelper overloads and snapshots marshal delegate set for helper generation.
tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/XAJavaInterop1/WriteUnnestedInterfaceTypes.txt Updates expected generated callbacks to use thunk + helper + __n_* body.
tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/XAJavaInterop1/WriteNestedInterfaceTypes.txt Updates expected generated callbacks to use thunk + helper + __n_* body.
tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/XAJavaInterop1/WriteNestedInterfaceClass.txt Updates expected generated callbacks to use thunk + helper + __n_* body.
tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/XAJavaInterop1/WriteMethodWithCharSequenceArrays.txt Updates expected generated callbacks to use thunk + helper + __n_* body.
tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/XAJavaInterop1/WriteInterfaceRedeclaredDefaultMethod.txt Updates expected generated callbacks to use thunk + helper + __n_* body.
tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/XAJavaInterop1/WriteInterfaceDefaultPropertyGetterOnly.txt Updates expected generated callbacks to use thunk + helper + __n_* body.
tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/XAJavaInterop1/WriteInterfaceDefaultProperty.txt Updates expected generated callbacks to use thunk + helper + __n_* body.
tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/XAJavaInterop1/WriteInterfaceDefaultMethod.txt Updates expected generated callbacks to use thunk + helper + __n_* body.
tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/XAJavaInterop1/WriteInterface.txt Updates expected generated callbacks to use thunk + helper + __n_* body.
tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/XAJavaInterop1/WriteDuplicateInterfaceEventArgs.txt Updates expected generated callbacks to use thunk + helper + __n_* body.
tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/XAJavaInterop1/WriteDefaultInterfaceMethodInvoker.txt Updates expected generated callbacks to use thunk + helper + __n_* body.
tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/XAJavaInterop1/WriteClass.txt Updates expected generated callbacks to use thunk + helper + __n_* body.
tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/XAJavaInterop1-NRT/WriteMethodWithCharSequenceArrays.txt Updates expected NRT callbacks to use thunk + helper + __n_* body.
tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/XAJavaInterop1-NRT/WriteInterface.txt Updates expected NRT callbacks to use thunk + helper + __n_* body.
tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/XAJavaInterop1-NRT/WriteClass.txt Updates expected NRT callbacks to use thunk + helper + __n_* body.
tests/generator-Tests/expected.xaji/TestInterface/Test.ME.TestInterfaceImplementation.cs Updates expected generated callbacks to use thunk + helper + __n_* body.
tests/generator-Tests/expected.xaji/TestInterface/Test.ME.ITestInterface.cs Updates expected generated callbacks to use thunk + helper + __n_* body.
tests/generator-Tests/expected.xaji/TestInterface/Test.ME.IGenericPropertyInterface.cs Updates expected generated callbacks to use thunk + helper + __n_* body.
tests/generator-Tests/expected.xaji/TestInterface/Test.ME.IGenericInterface.cs Updates expected generated callbacks to use thunk + helper + __n_* body.
tests/generator-Tests/expected.xaji/TestInterface/Test.ME.GenericStringPropertyImplementation.cs Updates expected generated callbacks to use thunk + helper + __n_* body.
tests/generator-Tests/expected.xaji/TestInterface/Test.ME.GenericStringImplementation.cs Updates expected generated callbacks to use thunk + helper + __n_* body.
tests/generator-Tests/expected.xaji/TestInterface/Test.ME.GenericObjectPropertyImplementation.cs Updates expected generated callbacks to use thunk + helper + __n_* body.
tests/generator-Tests/expected.xaji/TestInterface/Test.ME.GenericImplementation.cs Updates expected generated callbacks to use thunk + helper + __n_* body.
tests/generator-Tests/expected.xaji/TestInterface/Java.Util.IQueue.cs Updates expected generated callbacks to use thunk + helper + __n_* body.
tests/generator-Tests/expected.xaji/TestInterface/Java.Util.IDeque.cs Updates expected generated callbacks to use thunk + helper + __n_* body.
tests/generator-Tests/expected.xaji/TestInterface/Java.Util.ICollection.cs Updates expected generated callbacks to use thunk + helper + __n_* body.
tests/generator-Tests/expected.xaji/TestInterface/IInterfaceWithoutNamespace.cs Updates expected generated callbacks to use thunk + helper + __n_* body.
tests/generator-Tests/expected.xaji/TestInterface/ClassWithoutNamespace.cs Updates expected generated callbacks to use thunk + helper + __n_* body.
tests/generator-Tests/expected.xaji/TestInterface/NamespaceMapping.cs Adds expected __JniMarshalMethodHelper and updates delegate list expectations.
tests/generator-Tests/expected.xaji/Streams/Java.Lang.Throwable.cs Updates expected generated callbacks to use thunk + helper + __n_* body.
tests/generator-Tests/expected.xaji/Streams/Java.IO.OutputStream.cs Updates expected generated callbacks to use thunk + helper + __n_* body.
tests/generator-Tests/expected.xaji/Streams/Java.IO.IOException.cs Updates expected generated callbacks to use thunk + helper + __n_* body.
tests/generator-Tests/expected.xaji/Streams/Java.IO.FilterOutputStream.cs Updates expected generated callbacks to use thunk + helper + __n_* body.
tests/generator-Tests/expected.xaji/Streams/NamespaceMapping.cs Adds expected __JniMarshalMethodHelper overload set for Streams scenario.
tests/generator-Tests/expected.xaji/ParameterXPath/Xamarin.Test.A.cs Updates expected generated callbacks to use thunk + helper + __n_* body.
tests/generator-Tests/expected.xaji/ParameterXPath/NamespaceMapping.cs Adds expected __JniMarshalMethodHelper overloads for ParameterXPath scenario.
tests/generator-Tests/expected.xaji/NormalProperties/Xamarin.Test.SomeObject.cs Updates expected generated callbacks to use thunk + helper + __n_* body.
tests/generator-Tests/expected.xaji/NormalProperties/NamespaceMapping.cs Adds expected __JniMarshalMethodHelper overloads for NormalProperties scenario.
tests/generator-Tests/expected.xaji/NormalMethods/Xamarin.Test.C.cs Updates expected generated callbacks to use thunk + helper + __n_* body.
tests/generator-Tests/expected.xaji/NormalMethods/Xamarin.Test.A.cs Updates expected generated callbacks to use thunk + helper + __n_* body.
tests/generator-Tests/expected.xaji/NormalMethods/NamespaceMapping.cs Adds expected __JniMarshalMethodHelper overloads for NormalMethods scenario.
tests/generator-Tests/expected.xaji/NestedTypes/Xamarin.Test.NotificationCompatBase.cs Updates expected generated callbacks to use thunk + helper + __n_* body.
tests/generator-Tests/expected.xaji/NestedTypes/NamespaceMapping.cs Adds expected __JniMarshalMethodHelper overloads for NestedTypes scenario.
tests/generator-Tests/expected.xaji/java.lang.Enum/Java.Lang.IComparable.cs Updates expected generated callbacks to use thunk + helper + __n_* body.
tests/generator-Tests/expected.xaji/java.lang.Enum/NamespaceMapping.cs Adds expected __JniMarshalMethodHelper overloads for java.lang.Enum scenario.
tests/generator-Tests/expected.xaji/InterfaceMethodsConflict/Xamarin.Test.SomeObject2.cs Updates expected generated callbacks to use thunk + helper + __n_* body.
tests/generator-Tests/expected.xaji/InterfaceMethodsConflict/Xamarin.Test.SomeObject.cs Updates expected generated callbacks to use thunk + helper + __n_* body.
tests/generator-Tests/expected.xaji/InterfaceMethodsConflict/Xamarin.Test.II2.cs Updates expected generated callbacks to use thunk + helper + __n_* body.
tests/generator-Tests/expected.xaji/InterfaceMethodsConflict/Xamarin.Test.II1.cs Updates expected generated callbacks to use thunk + helper + __n_* body.
tests/generator-Tests/expected.xaji/InterfaceMethodsConflict/NamespaceMapping.cs Adds expected __JniMarshalMethodHelper overloads for conflict scenario.
tests/generator-Tests/expected.xaji/GenericArguments/Com.Google.Android.Exoplayer.Drm.IExoMediaDrm.cs Updates expected generated callbacks to use thunk + helper + __n_* body.
tests/generator-Tests/expected.xaji/GenericArguments/Com.Google.Android.Exoplayer.Drm.IExoMediaCrypto.cs Updates expected generated callbacks to use thunk + helper + __n_* body.
tests/generator-Tests/expected.xaji/GenericArguments/NamespaceMapping.cs Adds expected __JniMarshalMethodHelper overloads for higher-arity GenericArguments scenario.
tests/generator-Tests/expected.xaji/CSharpKeywords/Xamarin.Test.CSharpKeywords.cs Updates expected generated callbacks to use thunk + helper + __n_* body.
tests/generator-Tests/expected.xaji/CSharpKeywords/Java.Lang.Throwable.cs Updates expected generated callbacks to use thunk + helper + __n_* body.
tests/generator-Tests/expected.xaji/CSharpKeywords/NamespaceMapping.cs Adds expected __JniMarshalMethodHelper overloads for CSharpKeywords scenario.
tests/generator-Tests/expected.xaji/Core_Jar2Xml/Android.Views.View.cs Updates expected generated callbacks to use thunk + helper + __n_* body.
tests/generator-Tests/expected.xaji/Core_Jar2Xml/Android.Text.SpannableStringInternal.cs Updates expected generated callbacks to use thunk + helper + __n_* body.
tests/generator-Tests/expected.xaji/Core_Jar2Xml/Android.Text.SpannableString.cs Updates expected generated callbacks to use thunk + helper + __n_* body.
tests/generator-Tests/expected.xaji/Core_Jar2Xml/Android.Text.ISpanned.cs Updates expected generated callbacks to use thunk + helper + __n_* body.
tests/generator-Tests/expected.xaji/Core_Jar2Xml/Android.Text.ISpannable.cs Updates expected generated callbacks to use thunk + helper + __n_* body.
tests/generator-Tests/expected.xaji/Core_Jar2Xml/NamespaceMapping.cs Adds expected __JniMarshalMethodHelper overloads for Core_Jar2Xml scenario.
tests/generator-Tests/expected.xaji/Android.Graphics.Color/Xamarin.Test.SomeObject.cs Updates expected generated callbacks to use thunk + helper + __n_* body.
tests/generator-Tests/expected.xaji/Android.Graphics.Color/NamespaceMapping.cs Adds expected __JniMarshalMethodHelper overloads for Android.Graphics.Color scenario.
tests/generator-Tests/expected.xaji/Adapters/Xamarin.Test.GenericReturnObject.cs Updates expected generated callbacks to use thunk + helper + __n_* body.
tests/generator-Tests/expected.xaji/Adapters/Xamarin.Test.AdapterView.cs Updates expected generated callbacks to use thunk + helper + __n_* body.
tests/generator-Tests/expected.xaji/Adapters/Xamarin.Test.AbsSpinner.cs Updates expected generated callbacks to use thunk + helper + __n_* body.
tests/generator-Tests/expected.xaji/Adapters/NamespaceMapping.cs Adds expected __JniMarshalMethodHelper overloads for Adapters scenario.
tests/generator-Tests/expected.xaji/AccessModifiers/Xamarin.Test.TestClass.cs Updates expected generated callbacks to use thunk + helper + __n_* body.
tests/generator-Tests/expected.xaji/AccessModifiers/Xamarin.Test.PublicClass.cs Updates expected generated callbacks to use thunk + helper + __n_* body.
tests/generator-Tests/expected.xaji/AccessModifiers/Xamarin.Test.IExtendedInterface.cs Updates expected generated callbacks to use thunk + helper + __n_* body.
tests/generator-Tests/expected.xaji/AccessModifiers/Xamarin.Test.ExtendPublicClass.cs Updates expected generated callbacks to use thunk + helper + __n_* body.
tests/generator-Tests/expected.xaji/AccessModifiers/Xamarin.Test.BasePublicClass.cs Updates expected generated callbacks to use thunk + helper + __n_* body.
tests/generator-Tests/expected.xaji/AccessModifiers/NamespaceMapping.cs Adds expected __JniMarshalMethodHelper overloads for AccessModifiers scenario.

Copilot's findings

  • Files reviewed: 79/79 changed files
  • Comments generated: 1

Comment thread tools/generator/SourceWriters/MethodCallback.cs Outdated
@simonrozsival simonrozsival force-pushed the copilot/issue-1359-investigation branch 2 times, most recently from b315f7d to 22db48f Compare June 9, 2026 14:22
@simonrozsival

Copy link
Copy Markdown
Member Author

Follow-up from review discussion: I tried making the generated __n_* body a static local function inside n_* to avoid source-level naming concerns. It compiles and tests pass, but it significantly worsens Mono.Android artifact sizes.

Release comparison:

Shape Runtime Mono.Android.dll Ref Mono.Android.dll PDB generated mcw .cs
Baseline 42,798,592 19,295,744 32,553,308 29,234,484
Shared helper + sibling __n_* 42,121,728 19,339,776 30,690,252 28,508,893
Shared helper + local static __n_* 44,582,400 23,309,824 30,711,020 28,465,999

The local-function shape saves ~43 KB of generated source compared to sibling __n_*, but increases runtime Mono.Android.dll by ~2.46 MB and ref Mono.Android.dll by ~3.97 MB compared to the sibling shape. I reverted the PR branch back to the sibling __n_* shape because artifact size is the primary goal here.

@simonrozsival simonrozsival force-pushed the copilot/issue-1359-investigation branch from 22db48f to b315f7d Compare June 9, 2026 14:37
Split generated JNI callbacks into a tiny native entry thunk plus a body method, and centralize the marshal transition and exception handling in generated SafeInvoke helpers.

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

Copy link
Copy Markdown
Member Author

To address the visibility/naming concern without regressing artifact size, I kept the sibling __n_* body methods but made them explicitly private static. This preserves the smaller assembly/ref sizes from the sibling-method shape while making the intended visibility clear.

I also measured the static-local-function alternative: it saves a tiny amount of generated source but increases Release runtime Mono.Android.dll by ~2.46 MB and ref Mono.Android.dll by ~3.97 MB compared to sibling __n_*, so I did not keep that approach.

@simonrozsival simonrozsival force-pushed the copilot/issue-1359-investigation branch from b315f7d to 9e6df59 Compare June 9, 2026 14:41
@simonrozsival simonrozsival added the ready-to-review This PR is ready to review/merge, thanks! label Jun 9, 2026
@jonathanpeppers

Copy link
Copy Markdown
Member

/review

@github-actions

github-actions Bot commented Jun 9, 2026

Copy link
Copy Markdown

Java.Interop PR Reviewer completed successfully!

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

✅ LGTM — solid size optimization with clean design

Summary: This PR centralizes the JNI marshal boundary boilerplate (BeginMarshalMethod / try / catch / OnUserUnhandledException / EndMarshalMethod) into shared SafeInvokeAction/SafeInvokeFunc helpers in JniMarshal, replacing per-callback inline copies with thin n_* forwarding thunks that delegate to __n_* callback bodies via managed function pointers.

What's good

  • Impressive size savings: ~660 KB off Mono.Android.dll (Release), ~1.8 MB off PDB, ~725 KB off generated source
  • Correct [DebuggerDisableUserUnhandledExceptions] placement: follows the catch block from the generated n_* callbacks to the shared helpers
  • Clean attribute class removal: DebuggerDisableUserUnhandledExceptionsAttributeAttr is fully removed with zero dangling references
  • Comprehensive test updates: 28 expected output files updated consistently
  • Well-documented design rationale: PR description explains the tailcall/arity-cliff investigation and why fnptr-last was chosen

Issues

Severity Count
💡 suggestion 3

Notes

  • CI: Only license/cla check is visible. Build/test CI results were not available at review time — verify CI is green before merging.
  • The 34 SafeInvoke overloads go up to T0–T15 (16 extra parameters). This should cover virtually all Java methods, but if a binding exceeds this arity the generator will emit an uncompilable call. Worth documenting the limit or adding a generator diagnostic.
  • The Common/ expected output files (WriteInterfaceRedeclaredDefaultMethod.txt, WriteDuplicateInterfaceEventArgs.txt) still contain the old inline pattern — verified these are only used by non-XAJavaInterop1 target tests and are not stale.

Generated by Java.Interop PR Reviewer for issue #1452 · ● 9.4M

Comment thread tools/generator/SourceWriters/MethodCallback.cs Outdated
Comment thread src/Java.Interop/Java.Interop/JniMarshal.cs Outdated
Comment thread src/Java.Interop/Java.Interop/JniMarshal.cs Outdated
simonrozsival and others added 5 commits June 12, 2026 10:43
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@simonrozsival

Copy link
Copy Markdown
Member Author

/review

@simonrozsival

Copy link
Copy Markdown
Member Author

/azp run

@azure-pipelines

Copy link
Copy Markdown
No pipelines are associated with this pull request.

@github-actions

github-actions Bot commented Jun 12, 2026

Copy link
Copy Markdown

Java.Interop PR Reviewer completed successfully!

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

✅ LGTM — Clean, well-designed size optimization

Issue counts: 0 ❌ errors · 0 ⚠️ warnings · 1 💡 suggestion

Summary

This PR cleanly splits JNI callbacks into thin n_* forwarding thunks and __n_* marshal bodies, centralizing the exception-handling boilerplate into shared SafeInvokeAction/SafeInvokeFunc helpers in Java.Interop.JniMarshal. The approach is sound:

  • Exception boundary correctness[DebuggerDisableUserUnhandledExceptions] correctly follows the catch block into the shared helpers. The BeginMarshalMethod/OnUserUnhandledException/EndMarshalMethod pattern matches the existing ManagedPeer.cs convention.
  • NRT handling[return: MaybeNull] on SafeInvokeFunc avoids the null-forgiving operator while correctly handling the default return for non-nullable TResult types. Good call.
  • T4 template — Addresses prior review feedback. The template correctly generates all 34 overloads (17 arities × Action/Func) with consistent structure. The .csproj wiring (TextTemplatingFileGenerator + DependentUpon) follows the existing patterns for JniBuiltinMarshalers.tt and others.
  • Attribute propagationWriteMarshalBody correctly re-emits Obsolete and SupportedOSPlatform attributes on __n_* methods to suppress compiler warnings when the underlying Java method is deprecated.
  • Accessibility__n_* methods are always private static, which is correct for both class members (C# default) and interface members (explicit private required in C# 8+).
  • Generator cleanupDebuggerDisableUserUnhandledExceptionsAttributeAttr is cleanly removed with no remaining references.
  • Test coverage — All expected codegen output files (69 files) are consistently updated to reflect the new pattern.

The PublicAPI surface additions (34 entries in PublicAPI.Unshipped.txt) are complete and match the generated overloads.

CI Status

⏳ The dotnet.java-interop Azure Pipelines build is currently queued/pending on the latest commit (1e9ef1b). The license/cla check passed. Full CI verification is still needed before merge.

Positive callouts

  • The measured size reductions are significant: -677 KB runtime Mono.Android.dll, -726 KB generated source. Good use of shared infrastructure.
  • The design notes section in the PR description is excellent — documenting the tail. / arity-cliff investigation and the function-pointer-last rationale helps future maintainers understand why this shape was chosen.
  • Migrating from hand-written 530-line boilerplate to a 67-line T4 template eliminates copy-paste inconsistency risk.

Generated by Java.Interop PR Reviewer for issue #1452 · ● 9M

Comment thread src/Java.Interop/Java.Interop/JniMarshal.SafeInvoke.tt
Comment thread src/Java.Interop/Java.Interop/JniMarshal.SafeInvoke.tt Outdated
Co-authored-by: simonrozsival <374616+simonrozsival@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready-to-review This PR is ready to review/merge, thanks!

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Reduce IL size in generator output

4 participants