Skip to content

Commit f63eab9

Browse files
Reduce generated JNI callback IL size (#1452)
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: ```csharp 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 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent a961e99 commit f63eab9

69 files changed

Lines changed: 1802 additions & 1816 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

external/Java.Interop/src/Java.Interop/Java.Interop.csproj

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,10 @@
6060
<Generator>TextTemplatingFileGenerator</Generator>
6161
<LastGenOutput>JniPeerMembers.JniInstanceMethods_Invoke.cs</LastGenOutput>
6262
</None>
63+
<None Include="Java.Interop\JniMarshal.SafeInvoke.tt">
64+
<Generator>TextTemplatingFileGenerator</Generator>
65+
<LastGenOutput>JniMarshal.SafeInvoke.cs</LastGenOutput>
66+
</None>
6367
<None Include="Java.Interop\JniPeerMembers.JniFields.tt">
6468
<Generator>TextTemplatingFileGenerator</Generator>
6569
<LastGenOutput>JniPeerMembers.JniFields.cs</LastGenOutput>
@@ -86,6 +90,9 @@
8690
<Compile Condition=" '$(EnableDefaultCompileItems)' == 'true' " Update="Java.Interop\JniPeerMembers.JniInstanceMethods_Invoke.cs">
8791
<DependentUpon>JniPeerMembers.JniInstanceMethods_Invoke.tt</DependentUpon>
8892
</Compile>
93+
<Compile Condition=" '$(EnableDefaultCompileItems)' == 'true' " Update="Java.Interop\JniMarshal.SafeInvoke.cs">
94+
<DependentUpon>JniMarshal.SafeInvoke.tt</DependentUpon>
95+
</Compile>
8996
</ItemGroup>
9097
<ItemGroup>
9198
<PackageReference Include="Microsoft.CodeAnalysis.PublicApiAnalyzers" Version="4.14.0">

0 commit comments

Comments
 (0)