Avoid try/finally for keep-alive-only generated bindings
- Dominant language
- C#
- Stars
- 2.1k
- Forks
- 579
- Avg merge
- 1d 20h
- Merged PRs (30d)
- 257
Description
### Android framework version
net11.0-android (Preview)
### Affected platform version
`dotnet/android` main; generated C# bindings across supported target frameworks and runtimes.
### Description
Generated C# bindings currently use a `try`/`finally` block even when the `finally` contains only `GC.KeepAlive()` calls for Java reference arguments. Exception-handling regions prevent RyuJIT from inlining otherwise-thin binding wrappers and add EH metadata/code size.
When no marshaling cleanup is required, the generator could instead emit:
1. The JNI invocation, storing any return value in a local.
2. `GC.KeepAlive()` for each managed argument that supplied a borrowed JNI handle.
3. Return the stored result.
For example:
```csharp
var result = _members.InstanceMethods.InvokeVirtualObjectMethod (__id, this, __args);
GC.KeepAlive (value);
return result;
```
The existing `try`/`finally` shape should remain whenever the generated method must delete local JNI references, dispose marshaler state, unpin memory, copy arrays back, or perform any other cleanup on exceptional exits.
This should be evaluated for generated C# bindings to Java APIs, including methods, properties, and constructors. The change should preserve argument and receiver lifetime guarantees related to #5405 across MonoVM, CoreCLR, and NativeAOT.
Validation should include generator expected-output tests, concurrent-GC lifetime stress coverage, exception-path coverage, and benchmarks or generated-IL/code-size comparisons demonstrating whether removing keep-alive-only EH regions enables useful inlining or otherwise reduces overhead.
### Steps to Reproduce
1. Generate a C# binding method with a Java reference parameter that requires no temporary local-reference cleanup.
2. Inspect the generated method.
3. Observe that the JNI invocation is wrapped in `try`/`finally` solely to execute `GC.KeepAlive(parameter)`.
4. Inspect the resulting IL/JIT diagnostics and observe the EH clause, which prevents method inlining.
### Did you find any workaround?
Handwritten bindings can store the JNI result in a local, call `GC.KeepAlive()` after the invocation, and then return the result. There is no project-wide workaround for generated bindings.
### Relevant log output
N/A
Contributor guide
No contributing guide indexed for this repository
Research direction
Start in the C# binding generator logic that distinguishes keep-alive-only cases from real cleanup, then inspect the generated-output tests. Add coverage for methods, properties, and constructors, plus concurrent-GC lifetime and exception-path tests; use benchmarks or generated-IL/code-size comparisons to validate the optimization. Done means keep-alive-only bindings avoid EH regions while cleanup cases and lifetime guarantees remain unchanged across MonoVM, CoreCLR, and NativeAOT.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- android, csharp
- Domain
- mobile-dev, performance, testing
- Issue type
- Refactor
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100