Skip to content

Use shared managed thunks for reflection invocation - #133662

Draft
jkoritzinsky wants to merge 5 commits into
mainfrom
dev/jkoritzinsky/custom-attribute-stack
Draft

Use shared managed thunks for reflection invocation#133662
jkoritzinsky wants to merge 5 commits into
mainfrom
dev/jkoritzinsky/custom-attribute-stack

Conversation

@jkoritzinsky

@jkoritzinsky jkoritzinsky commented Sep 11, 2026

Copy link
Copy Markdown
Member

Summary

Layer 1 of the stack for #123864, building on am11's #126542 and the requested split in #126542 (comment). The dependent layers are #133658 (custom attributes), #133659 (func-eval), and #133660 (dead native machinery cleanup).

  • Use shared, precompilable managed thunks for common MethodInfo.Invoke, ConstructorInfo.Invoke, MethodInvoker, and ConstructorInvoker shapes instead of the native reflection dispatcher.
  • Cover bounded reference/primitive families: ordinary reference-type instance/delegate calls, reference constructors through eight arguments, selected primitive/enum-bearing constructors and methods, and targeted static/reference-byref patterns. Comments identify the ASP.NET Core callsites motivating the specialized shapes.
  • Resolve virtual/interface targets for the actual receiver, and preserve declared-type argument validation, enum widths/result identity, reference copy-back, and collectible ownership.
  • Keep unsupported signatures on the cached emitted fallback; this does not add general struct/nullable/value-type-receiver support.
  • Stay shared for the first 10,000 calls, then specialize starting on call 10,001. Promotion and every specialization strategy require RuntimeFeature.IsDynamicCodeCompiled, not merely IsDynamicCodeSupported.
  • Preserve existing-object constructor invocation and make ReadyToRun's explicit-this calli rewrite agree with the VM intrinsic.

No public API is added.

Validation

The threshold update passed 1,964 reflection + 631 forced-emitted + 631 forced-shared cases on Windows x64 Checked CoreCLR. Exact boundary tests cover the object, span, and byref strategies; the old 100-call runtime fails those tests as expected. A controlled CoreLib configuration with dynamic code supported but not compiled passed another 631 + 631 forced-mode cases and retained shared invocation beyond 10,000 calls. That configuration is policy coverage, not a claim of executing a native compiler-less platform.

The bounded matrix includes virtual/interface/generic-virtual/default-interface dispatch, delegate DynamicInvoke with static and multicast callbacks, enum widths/results, reference out-parameter success/false/exception behavior, moving GC, unsupported-shape boundaries, and collectible methods. Later stack validation also executed the invocation suites on Windows x86. Browser/WASI managed CoreLib/ABI generation was checked; actual browser/WASI and big-endian runtime execution was not performed.

Performance evidence

Local measurements used a Windows x64 EPYC 7763 Hyper-V VM. The ASP.NET Core survey at dotnet/aspnetcore revision a2ac63c3a56d establishes source-level invocation demand, not measured performance rankings.

Adding the bounded families avoided approximately 1.5–5 KiB of managed allocation per first invocation versus the earlier stack's emitted fallback. An actual ASP.NET Core 10.0.9 UseMiddleware/ActivatorUtilities eight-reference-constructor startup probe measured median 16.8753 → 14.4399 ms and 9,560 → 5,376 bytes across seven paired fresh-process runs. This did not measure ordinary compiled request dispatch or default-constructor Activator paths.

For the separate 100 → 10,000 threshold comparison, matching Release native hosts and baseline/changed CoreLib were run with ReadyToRun disabled for both. Seventy-two fresh-process observations confirmed the actual boundary. Selected warmed BenchmarkDotNet results were:

Shape Threshold 100 Threshold 10,000 Managed allocation
Eight-reference constructor 252.30 ns 228.15 ns 24 B in both
Instance primitive getter 26.57 ns 25.25 ns 24 B in both
Static four-reference method 115.95 ns 94.97 ns 0 B in both

These are short VM-local measurements, not universal throughput guarantees. Earlier expanded-shape testing at threshold 100 exposed a profile-sensitive warmed ConstructorInfo.Invoke eight-argument case at approximately 1.30× baseline; tiering-disabled/direct-loop controls and the actual ConstructorInvoker middleware API did not reproduce that effect. The threshold comparison above is a separate experiment.


Stack created with GitHub Stacks CLIGive Feedback 💬

Note

This PR description and implementation were prepared with GitHub Copilot. Commits that reuse am11's code retain the requested co-author attribution.

jkoritzinsky and others added 5 commits September 9, 2026 13:22
Port the MethodInfo.Invoke layer of #126542 toward #123864. Reuse the managed emit pipeline, promote common shared thunks after 100 calls, cache collectible fallbacks, and remove the native invocation dispatcher and CoreCLR debugger workaround. Preserve constructor invocation on existing instances and cover tier transitions and collectible lifetimes.

Co-authored-by: Adeel Mujahid <3840695+am11@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Cover ordinary instance and delegate calls, extended reference constructors, targeted primitive and enum inputs, and reference-only static and byref patterns. Resolve virtual targets per receiver, retain emitted fallback for unsupported shapes, and preserve the existing specialization threshold. Match explicit-this calli rewriting in ReadyToRun and exercise shared selection, dispatch, copy-back, GC, exclusions, and promotion.

Co-authored-by: Adeel Mujahid <3840695+am11@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Require compiled dynamic-code support both for shared-thunk promotion and for all specialized argument strategies. Preserve the emitted fallback needed for otherwise unsupported signatures. Exercise the exact shared/specialized boundary and retain shared invocation beyond it when compilation is unavailable.

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

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 3 pipeline(s).
13 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @steveisok, @dotnet/area-system-reflection
See info in area-owners.md if you want to be subscribed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

1 participant